Принцип инверсии зависимостей (DIP) в Python
В этом уроке вы узнаете о принципе инверсии зависимостей в 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()