Шаблон проектирования декоратор

Во второй статье цикла, посвящённого шаблонам проектирования, я хотел бы поговорить про шаблон проектирования «декоратор», который я частенько путаю с шаблоном «адаптер». О последнем, кстати, я подробно рассказывал в предыдущей статье. Впрочем, оба шаблона проектирования делят одно альтернативное название — «обёртка» (Wrapper).

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

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

Реализация декоратора

Для реализации шаблона проектирования «декоратор» потребуется как минимум два класса: класс или интерфейс декорируемого объекта и класс-декоратор, который будет содержать этот самый объект и расширять его поведение. Класс-декоратор может быть расширен другими классами.

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

Кроме этого я создал два класса, реализующих этот интерфейс. Теперь мне хочется добавить возможность работы с кэшем при поиске задач: класс-декоратор должен сначала попытаться найти задачу в кэше, а если она не найдена — обратиться к декорируемому объекту. И если декорируемый объект вернёт задачу, то она должна быть сохранена в кэше.

Класс-декоратор можно описать следующим образом:

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

Кстати, метод findTaskById в классе-декораторе реализует шаблон проектирования «шаблонный метод».

CachingFindTaskByIdDecorator можно назвать абстрактным декоратором, теперь можно реализовать конкретный декоратор, использующий API кэширования из Spring Framework.

Теперь класс SpringCachingFindTaskByIdDecorator может быть использован для добавления кэширования при работе с любыми реализациями FindTaskByIdSpi, как это показано ниже:

Тестирование

Логика тестирования классов-декораторов полностью соответствует логике тестирования классов, реализующих шаблон проектирования «адаптер объекта»: нужно протестировать собственную логику класса-декоратора и корректность его обращения к объекту-делегату.

Класс SpringCachingFindTaskByIdDecorator можно протестировать следующим образом:

Выводы

Как уже было сказано выше, шаблон проектирования «декоратор» позволяет добавить новые свойства и методы существующему объекту без внесения изменения в код его класса и без необходимости использования наследования. Применение этого шаблона проектирования добавляет гибкости коду и позволяет соблюдать принцип открытости/закрытости и принцип разделения интерфейсов.

Несмотря на то, что шаблон проектирования «декоратор» идеологически близок шаблону проектирования «адаптер», он имеет существенное отличие от последнего — класс-декоратор реализует интерфейс декорируемого класса, в то время как класс-адаптер реализует интерфейс, с которым адаптируемый объект изначально не совместим.