Decorator pattern
Не подумайте, меня не покусал Егор Бугаенко (для тех кто в теме, кто нет — погуглите), мои взгляды не настолько радикальны, однако я действительно считаю, что этому паттерну уделяют мало внимания. Потому решил познакомить с ним тех, кто не знаком и освежить в памяти для тех, кто уже давно им пользуется.
Декоратор — (wiki) структурный шаблон проектирования, предназначенный для динамического подключения дополнительного поведения к объекту.
👉 Если простым языком, то суть данного паттерна заключается в "оборачивании" существующего объекта новым функционалом, при этом оригинальный интерфейс объекта остается неизменным. В данном примере WrapperObject и есть наш декоратор.
❓ В чем преимущества?
1. Его не получится просто так создать без существования базового класса.
2. Так как базовый класс и класс обертка имплементируют один интерфейс — то они взаимозаменяемы.
3. Мы расширяем поведение без изменения исходного кода.
Стоп, да это же прям Open-Closed Principle! И да, это отличная альтернатива наследованию. Также вы можете использовать несколько разных обёрток одновременно.
❗️ Да, клиентский код выглядит не круто, да и искать все места где вызывается базовый класс, чтобы прицепить обёртку бывает проблематично (особенно в долгоживущих проектах). Однако в фреймворках этот вопрос легко решается. Например в Symfony достаточно добавить всего несколько строк:
Представим, что у нас есть какой-то Mailer, который мы описали в services.yaml и теперь мы хотим логировать отправку всей почты. Для этого у контейнера есть директива decorates. Подобная запись подменит основной mailer на mailer_logger и наши письма начнут начнут логироваться. Для ссылки на исходный класс нужно использовать decorating_service_id + .inner (или просто '@.inner' начиная с Symfony 5.1).
👍 Но что если мы хотим не только логировать, но и отправлять наши письма через очередь? Нет проблем, мы можем добавить еще одну запись. Но как задать порядок в котором будут вызваны декораторы? Для этого существует директива decoration_priority (по умолчанию 0). Соответственно чем выше приоритет — тем раньше применится декоратор (всё логично). Например такой код сначала залогирует (1), а потом уже отправит в очередь (2) наше сообщение:
$this->services['mailer'] = new LoggerDecorator(new QueueDecorator(new Mailler()));
#php #oop #patterns #middle