Зарегистрируйтесь, чтобы продолжить обучение

Инверсия зависимостей Python: Полиморфизм

В современной разработке программного обеспечения важно создавать гибкий и поддерживаемый код, который легко адаптируется к изменениям и расширяется с минимальными затратами.

Одной из основных проблем, с которыми разработчики сталкиваются, является зависимость модулей верхнего уровня от модулей нижнего уровня. Это приводит к тесной связи и усложнению процесса изменения кода. Здесь на помощь приходит принцип инверсии зависимостей (Dependency Inversion Principle или DIP), который входит в состав принципов SOLID — основ объектно-ориентированного программирования.

В этом уроке мы рассмотрим применение принципа инверсии зависимостей и увидим, как он помогает создавать гибкий и расширяемый код.

Что такое DIP

Принцип инверсии зависимостей или Dependency Inversion Principle (DIP) — это один из принципов SOLID, которые являются основой объектно-ориентированного программирования.

SOLID — это акроним, который состоит из первых букв пяти принципов, а DIP — последний из них.

DIP гласит:

  • Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракций
  • Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций

В основе этого принципа лежит идея, что высокоуровневые модули, которые описывают правила бизнес-логики, не должны зависеть от низкоуровневых модулей, обеспечивающих выполнение базовых операций: чтение и запись в базу данных.

Это значит, что мы должны стараться делать наши модули независимыми друг от друга. Так мы можем легко менять одни модули без необходимости вносить изменения в другие.

Допустим, у нас есть класс User, который использует класс MySQLDatabase для сохранения информации о пользователях:

class MySQLDatabase:
    def save(self, data):
        print(f"Saving {data} to MySQL database")


class User:
    def __init__(self):
        self.database = MySQLDatabase()

    def save_user(self, data):
        self.database.save(data)

В этом примере класс User зависит от класса MySQLDatabase. Это означает, что если мы захотим заменить MySQLDatabase на другую базу данных, нам придется изменить класс User. Это нарушает DIP.

Чтобы исправить это, мы можем использовать абстракцию для создания общего интерфейса, который будет использоваться классом User:

class Database:
    def save(self, data):
        pass


class MySQLDatabase:
    def save(self, data):
        print(f"Saving {data} to MySQL database")


class User:
    def __init__(self, database):
        self.database = database

    def save_user(self, data):
        self.database.save(data)

Теперь класс User не зависит от конкретного класса MySQLDatabase, а зависит от абстракции Database. Это означает, что мы можем легко заменить MySQLDatabase на другую базу данных, которая поддерживает интерфейс Database. И в этом случае не нужно вносить какие-либо изменения в класс User.

Пример добавления новой базы данных

Теперь представим, что мы хотим использовать NoSQL базу данных MongoDB вместо MySQL. Для этого нужно создать новый класс MongoDBDatabase, который реализует метод save, и передать его в класс User:

class MongoDBDatabase:
    def save(self, data):
        print(f"Saving {data} to MongoDB database")


user = User(MongoDBDatabase())
user.save_user("user data")

Принцип инверсии зависимостей обеспечивает большую гибкость. Он позволяет легко заменить одну реализацию базы данных другой и не менять остальной код. Благодаря DIP наши классы становятся гибкими и адаптируемыми к изменениям.

Но есть еще способы инъекции зависимостей, которые помогают управлять зависимостями в нашем коде. Они позволяют передавать объекты, от которых зависит класс, через аргументы функции, конструктор или сеттеры. В каждом случае выбор метода инъекции зависимостей зависит от конкретного случая и требований к коду.

Способы инъекции зависимостей

Принцип инверсии зависимостей не только облегчает работу с кодом, но и открывает возможности для различных способов инъекции зависимостей. Всего существует три основных способа инъекции зависимостей:

  1. Инъекция через аргументы функций или методов — наиболее прямой и простой способ инъекции зависимостей. Зависимости передаются в качестве аргументов функции или метода, который их использует:

    def do_something_useful(logger):
        # some code
    
    do_something_useful(Logger())
    

    В этом примере функция do_something_useful принимает объект logger как аргумент, который используется внутри функции.

  2. Инъекция через конструктор — когда мы работаем с объектами, мы можем передавать зависимости через конструктор объекта:

    class Application:
        def __init__(self, logger):
            self.logger = logger
    
    app = Application(Logger())
    

    Здесь зависимость logger передается в конструктор класса Application и сохраняется внутри объекта для последующего использования.

  3. Инъекция через сеттеры — этот метод связан с изменением состояния объектов и может нарушить их целостность, поэтому его следует использовать с осторожностью:

    class Application:
        def set_logger(self, logger):
            self.logger = logger
    
    app = Application()
    app.set_logger(Logger())
    

    В этом примере объект logger устанавливается в объект Application после его создания через специальный метод (сеттер) set_logger.

За громким термином "инверсия зависимостей" скрывается очень простая штука — передача параметров. С другой стороны термины позволяют понять больше смысла без необходимости знать дополнительный контекст. Главное не увлекаться, а то можно превратиться в архитектурных астронавтов.

Выводы

Принцип инверсии зависимостей — важная часть SOLID. Он помогает писать более гибкий и поддерживаемый код. С помощью этого принципа мы можем делать модули независимыми друг от друга за счет уменьшения связанности в коде. Это позволяет легче вносить изменения в код и делает его более устойчивым к изменениям.

Важно помнить, что, как и любой принцип или паттерн проектирования, DIP имеет свои ограничения и не всегда применим. Его следует использовать там, где это уместно, и всегда с учетом контекста и требований проекта.


Дополнительные материалы

  1. Dependency Injection

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff