Принцип инверсии зависимостей (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.

Зависимость класса 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.

Определение класса 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()
Похожие посты
Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *