Слабосвязанный код Python с внедрением зависимостей

Jan 13 2023
Программное обеспечение должно быть гибким, чтобы реагировать на изменения. Внедрение зависимостей — это метод управления зависимостями между программными компонентами.

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

Внедрение зависимостей в Python

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

Создание абстракций с интерфейсами

Написание слабосвязанного кода Python с внедрением зависимостей состоит из трех шагов. Первый шаг — определить, какие зависимости нужны коду, второй шаг — создать интерфейсы для каждой зависимости, а третий шаг — передать их зависимому объекту через его конструктор или параметры метода.

Недавно я реализовал функциональность, которая требовала байтового кодировщика и декодера для манипулирования бизнес-данными ( я упростил ее реализацию для удобства чтения ). Я перенес их обязанности на интерфейсы. В Python нет ключевого слова interface, как в Golang, но вы можете воспроизвести функциональность интерфейса с помощью библиотеки ABC .

Интерфейсы для кодера и декодера.

Ориентируясь на IPacketEncoderинтерфейс, его должен реализовать конкретный класс кодировщика. Первоначальные бизнес-требования требовали, чтобы все типы были закодированы, а строки были дополнены их длиной. К счастью, Python предоставляет библиотеку struct для выполнения большей части тяжелой работы.

Конкретный класс PaddedPacketEncoder.

Реализация внедрения зависимостей

Функция encode_nameпредставляет собой простую реализацию для создания закодированной полезной нагрузки с указанным именем. Он принимает IPacketEncoderобъект и строку в качестве параметров.

Экземпляр PaddedPacketEncoder передается в функцию encode_name.

Две ключевые детали реализации — использование IPacketEncoderинтерфейса вместо PaddedPacketEncoderконкретного класса и encoderаргумент, а не инициализация внутри функции. Это внедрение зависимостей. Поскольку IPacketEncoderон передается, его можно легко расширить, изменить или снабдить заглушкой , тем самым сохраняя слабую связанность системы.

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

Конкретный класс NullTerminatedPacketEncoder.

NullTerminatedPacketEncoderпо существу добавляется NULL_BYTEпосле каждой закодированной строки. Возвращаясь к encode_nameфункции, экземпляр NullTerminatedPacketEncoderявляется допустимым аргументом кодировщика, поскольку NullTerminatedPacketEncoderреализует IPacketEncoderинтерфейс аналогично PaddedPacketEncoder.

NullTerminatedPacketEncoder передается в функцию encode_name.

Если бы здесь не использовалась инъекция зависимостей, это могло бы привести к сильно связанному коду, который трудно изменить, например:

Парное кодирование кода Python.

Если потребуется новый энкодер, encode_nameпридется почти полностью изменить функцию вместе с ее тестами, что продлит сроки поставки. Что еще хуже, это может привести к скрытым побочным эффектам, особенно если исходный разработчик больше недоступен.

В заключение я приведу менее упрощенный пример использования этой техники. SocketServiceотвечает за декодирование кода операции, вызов соответствующего обработчика и возврат результата. Все его типы аргументов являются абстракциями интерфейса, включая его конструктор. CLI отвечает за определение используемого кодировщика, т.е. NullTerminated или Padded, и передачу его экземпляра в SocketServiceкласс.

SocketService, использующий внедрение зависимостей.

Поскольку проекты продолжают расти, рекомендуется использовать инфраструктуру внедрения зависимостей для автоматического «внедрения» этих зависимостей, например Dependency Injector .

Надеюсь, это демонстрирует, как писать слабосвязанный код Python с внедрением зависимостей. Многим разработчикам Python не нравится внедрение зависимостей, потому что они считают его не «питоновским», но я бы сказал, что его преимущества не могут быть дискредитированы, особенно когда он присутствует во многих других языках программирования.

Спасибо за прочтение . Если вы хотите связаться со мной, вы можете найти меня на Linkedin или Github .