Принцип инверсии зависимостей (DIP)
Вот он — последний из пяти принципов ООП, венец коллекции, гвоздь программы. Ладно, другие принципы тоже важны:
S — принцип единой ответственности (Single responsibility Principle)
O — принцип открытости / закрытости (Open-closed Principle)
L — принцип подстановки Барбары Лисков (Liskov Substitution Principle)
I — принцип разделения интерфейса (Interface Segregation Principle)
D — принцип инверсии зависимостей (Dependency Inversion Principle)
Принцип инверсии зависимостей гласит:
- Модули высокого уровня не должны зависеть от модулей низкого уровня. И те, и другие должны зависеть от абстракций.
- Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
Принцип инверсии зависимостей направлен на уменьшение связи между классами путем создания между ними слоя абстракции.
Как вам, а? Погнали разбираться, кто что курил.
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 стороннего конвертора для перевода суммы из одной валюты в другую. Для простоты мы жестко закодировали обменный курс как 1,2. На практике для получения обменного курса вам придется обратиться к API.
У класса App
есть метод start()
, который использует экземпляр класса FXconverter
для конвертации 100 EUR в USD.
App
— это модуль, который в значительной степени зависит от класса FXConverter
, который, в свою очередь, зависит от API конвертора.
В будущем, если API конвертора изменится, это приведет к поломке кода. Также, если вы захотите использовать другой API, вам нужно будет изменить класс App
.
Чтобы всего этого избежать, необходимо инвертировать зависимость таким образом, чтобы класс FXConverter
адаптировался к классу App
.
Для этого вы определяете интерфейс и делаете App
зависимым от него, а не от класса FXConverter
. Затем вы изменяете FXConverter
так, чтобы он соответствовал интерфейсу.
Сначала определите абстрактный класс CurrencyConverter
, который будет действовать как интерфейс. Пусть у него будет метод convert()
, который должны реализовывать все подклассы CurrencyConverter
:
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
и его передачи в App
:
if __name__ == '__main__': converter = FXConverter() app = App(converter) app.star
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
без изменения последнего:
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()
Заключение
Используйте принцип инверсии зависимостей, чтобы сделать ваш код более надежным. Делайте высокоуровневый модуль зависимым от абстракции, а не от конкретной реализации.
Источник: Python Tutorial
Перевод и адаптация: Екатерина Прохорова
👉🏻Подписывайтесь на PythonTalk в Telegram 👈🏻