Python Dependency Inversion Principle

Summary: in this tutorial, you’ll learn about the Python dependency inversion principle to make your code hi

Introduction to the dependency inversion principle

The dependency inversion principle is one of the five SOLID principles in object-oriented programming:

The dependency inversion principle states that:

  • High-level modules should not depend on low-level modules. Both should depend on abstractions.
  • Abstractions should not depend on details. Details should depend on abstractions.

The dependency inversion principle aims to reduce the coupling between classes by creating an abstraction layer between them.

See the following example:

class FXConverter:
    def convert(self, from_currency, to_currency, amount):
        print(f'{amount} {from_currency} = {amount * 1.2} {to_currency}')
        return amount * 1.2


class App:
    def start(self):
        converter = FXConverter()
        converter.convert('EUR', 'USD', 100)


if __name__ == '__main__':
    app = App()
    app.start()Code language: Python (python)

In this example, we have two classes FXConverter and App.

The FXConverter class uses an API from an imaginary FX third-party to convert an amount from one currency to another. For simplicity, we hardcoded the exchange rate as 1.2. In practice, you will need to make an API call to get the exchange rate.

The App class has a start() method that uses an instance of the FXconverter class to convert 100 EUR to USD.

The App is a high-level module. However, The App depends heavily on the FXConverter class that is dependent on the FX’s API.

In the future, if the FX’s API changes, it’ll break the code. Also, if you want to use a different API, you’ll need to change the App class.

To prevent this, you need to invert the dependency so that the FXConverter class needs to adapt to the App class.

To do that, you define an interface and make the App dependent on it instead of FXConverter class. And then you change the FXConverter to comply with the interface.

First, define an abstract class CurrencyConverter that acts as an interface. The CurrencyConverter class has the convert() method that all of its subclasses must implement:

from abc import ABC


class CurrencyConverter(ABC):
    def convert(self, from_currency, to_currency, amount) -> float:
        pass
Code language: Python (python)

Second, redefine the FXConverter class so that it inherits from the CurrencyConverter class and implement the convert() method:

class FXConverter(CurrencyConverter):
    def convert(self, from_currency, to_currency, amount) -> float:
        print('Converting currency using FX API')
        print(f'{amount} {from_currency} = {amount * 1.2} {to_currency}')
        return amount * 2Code language: Python (python)

Third, add the __init__ method to the App class and initialize the CurrencyConverter‘s object:

class App:
    def __init__(self, converter: CurrencyConverter):
        self.converter = converter

    def start(self):
        self.converter.convert('EUR', 'USD', 100)Code language: Python (python)

Now, the App class depends on the CurrencyConverter interface, not the FXConverter class.

The following creates an instance of the FXConverter and pass it to the App:

if __name__ == '__main__':
    converter = FXConverter()
    app = App(converter)
    app.start()Code language: JavaScript (javascript)

Output:

Converting currency using FX API
100 EUR = 120.0 USD

In the future, you can support another currency converter API by subclassing the CurrencyConverter class. For example, the following defines the AlphaConverter class that inherits from the CurrencyConverter.

class AlphaConverter(CurrencyConverter):
    def convert(self, from_currency, to_currency, amount) -> float:
        print('Converting currency using Alpha API')
        print(f'{amount} {from_currency} = {amount * 1.2} {to_currency}')
        return amount * 1.15Code language: Python (python)

Since the AlphaConvert class inherits from the CurrencyConverter class, you can use its object in the App class without changing the App class:

if __name__ == '__main__':
    converter = AlphaConverter()
    app = App(converter)
    app.start()Code language: JavaScript (javascript)

Output:

Converting currency using Alpha API
100 EUR = 120.0 USD

Put it all together.

from abc import ABC


class CurrencyConverter(ABC):
    def convert(self, from_currency, to_currency, amount) -> float:
        pass


class FXConverter(CurrencyConverter):
    def convert(self, from_currency, to_currency, amount) -> float:
        print('Converting currency using FX API')
        print(f'{amount} {from_currency} = {amount * 1.2} {to_currency}')
        return amount * 1.15


class AlphaConverter(CurrencyConverter):
    def convert(self, from_currency, to_currency, amount) -> float:
        print('Converting currency using Alpha API')
        print(f'{amount} {from_currency} = {amount * 1.2} {to_currency}')
        return amount * 1.2


class App:
    def __init__(self, converter: CurrencyConverter):
        self.converter = converter

    def start(self):
        self.converter.convert('EUR', 'USD', 100)


if __name__ == '__main__':
    converter = AlphaConverter()
    app = App(converter)
    app.start()
Code language: Python (python)

Summary

  • Use the dependency inversion principle to make your code more robust by making the high-level module dependent on the abstraction, not the concrete implementation.
Did you find this tutorial helpful ?