Декоратор(Decorator)

Декоратор(Decorator)

KarmanovichDev

Первая часть - https://telegra.ph/DekoratorDecorator-04-06


Начну вторую часть статьи с обоснования, почему синтаксис @ не даёт нам использовать паттерн декоратор правильно. Обратимся к определению из wiki:


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


Обратите внимание на слово "динамического". Давайте размышлять, а насколько использование декораторов с помощью @ позволяет нам динамически добавлять поведение объекту?

Ответ прост и логичен: ни на сколько. Да, мы можем, не меняя кода обёртываемого объекта, добавлять/удалять дополнительное поведение. Да, клиентский код ничего не знает о подмене. Да, мы можем использовать несколько декораторов. Но, как всегда, есть одно "но": мы можем делать это только статически, до того как мы запустим нашу программу. После запуска программы объект обладает чётким набором действий, которые не поддаются изменению.


Вот пример идеологически правильного использования декораторов в python относительно паттерна decorator:

Когда пользователь производит вход в систему, то в зависимости от его настроек, он может получить об этом уведомления в удобном ему виде. Например, если кто-то произведёт авторизацию под моим аккаунтом(даже я, но с другого устройства), в любом популярном мессенджере, то я получу об этом уведомление.


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

  • UserService() - Базовое поведение
  • UserServiceEmailNotificator(UserService()) - Базовое поведение + уведомление пользователя по email
  • UserServicePushNotificator(UserServiceEmailNotificator(UserService())) - Базовое поведение + уведомления по email и push уведомление пользователя.

От вызова к вызову(у разных пользователей разные настройки), будет вызываться разное поведение. Если пользователь будет менять свои настройки, то и в рамках одного пользователя будет разное поведение при авторизации в приложении. Это и называется динамическое(на лету) добавление функциональности.


Что там насчёт декораторов с помощью классов?

Точно такой же декоратор для логирования, что и в первой части, только с помощью класса.


Пытливые умы могли заметить, что эта реализация декоратора очень похожа на одну из возможных реализаций паттерна заместитель(proxy). Они и правда похожи, но имеют свои отличия в ответственности и реализации. Я расскажу об этом в отдельной статье, где сравню эти два паттерна.


В завершении статьи хочу показать пару примеров, когда мы используем python decorator, но это не является pattern decorator:

В примере выше, я используя FastAPI.get, для указания фреймворку, что при вызове определённого url нужно вызвать требуемый объект(some_controller).

Вы спросите в чёт тут проблема? Проблема в том, что этот декоратор через внедрение зависимостей, передаёт внутрь some_controller объект request. Получается, он не добавляет поведение для some_controller, а выступает в роли клиента для использования этой функции фреймворком.


В этом примере используется библиотека для внедрения зависимостей(injector). Она помогает собирать за нас требуемые зависимости, выступая в роли DI Container - множество фабрик объектов объединены одним интерфейсом создания через контейнер зависимостей.

Является ли в этом случае @inject добавлением новой функциональности? Нет. К тому же если убрать его, то клиентский код изменится, так как теперь ему нужно будет собрать зависимость для Service как-то иначе.


В будущем я напишу третью часть статьи, где упомянем о:

  • Декоратор, через наследование
  • Декоратор с расширением интерфейса относительно декорируемого объекта

По сути, в третьей части мы реализуем самый близкий к изначальному виду декоратор


Report Page