В этом уроке вы узнаете о принципе инверсии зависимостей в Python, который поможет сделать ваш код более качественным.
Что такое принцип инверсии зависимостей в Python?
Принцип инверсии зависимостей (DIP) — один из пяти принципов SOLID в объектно-ориентированном программировании:
- S – Принцип единой ответственности
- O – Принцип открытости-закрытости
- L – Принцип подстановки Лисков
- I – Принцип разделения интерфейса
- D – Принцип инверсии зависимостей
Принцип инверсии зависимостей гласит:
- Модули высокого уровня не должны зависеть от модулей низкого уровня. Оба должны зависеть от абстракций.
- Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
Принцип инверсии зависимостей направлен на уменьшение связи между классами путем создания между ними уровня абстракции.
См. следующий пример:
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()
В этом примере у нас есть два класса FXConverter и App.
Класс FXConverter использует API воображаемой третьей стороны FX для конвертации суммы из одной валюты в другую. Для простоты мы жестко запрограммировали обменный курс как 1,2. На практике вам нужно будет сделать вызов API, чтобы получить обменный курс.
В классе App есть метод start(), который использует экземпляр класса FXconverter для конвертации 100 евро в доллары США.
Приложение представляет собой модуль высокого уровня. Однако приложение сильно зависит от класса FXConverter, который зависит от API FX.
В будущем, если API FX изменится, это приведет к поломке кода. Кроме того, если вы хотите использовать другой API, вам необходимо изменить класс приложения.
Чтобы предотвратить это, вам необходимо инвертировать зависимость, чтобы класс FXConverter мог адаптироваться к классу App.
Для этого вы определяете интерфейс и делаете приложение зависимым от него, а не от класса FXConverter. А затем вы меняете FXConverter в соответствии с интерфейсом.
- Сначала определите абстрактный класс CurrencyConverter, который действует как интерфейс. Класс CurrencyConverter имеет метод Convert(), который должны реализовать все его подклассы:
from abc import ABC
class CurrencyConverter(ABC):
def convert(self, from_currency, to_currency, amount) -> float:
pass
- Во-вторых, переопределите класс FXConverter, чтобы он наследовался от класса CurrencyConverter, и реализуйте метод Convert():
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 * 2
- В-третьих, добавьте метод __init__ в класс App и инициализируйте объект CurrencyConverter:
class App:
def __init__(self, converter: CurrencyConverter):
self.converter = converter
def start(self):
self.converter.convert('EUR', 'USD', 100)
Теперь класс App зависит от интерфейса CurrencyConverter, а не от класса FXConverter.
Следующий код создает экземпляр FXConverter и передает его приложению:
if __name__ == '__main__':
converter = FXConverter()
app = App(converter)
app.start()
Выход:
Converting currency using FX API 100 EUR = 120.0 USD
В будущем вы сможете поддерживать другой API-интерфейс конвертера валют, создав подкласс класса CurrencyConverter. Например, ниже определяется класс AlphaConverter, который наследуется от 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.15
Поскольку класс AlphaConvert наследуется от класса CurrencyConverter, вы можете использовать его объект в классе App, не меняя класс App:
if __name__ == '__main__':
converter = AlphaConverter()
app = App(converter)
app.start()
Выход:
Converting currency using Alpha API 100 EUR = 120.0 USD
Полный код:
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()
