Метафора
Как понятно из названия, данный паттерн чаще всего используется для расширения исходного объекта до требуемого вида. Например, мы условно можем считать «декоратором» человека с кистью и красной краской. Таким образом, какой бы объект (или определенный тип объектов) мы не передали в руки «декоратору», на выходе мы будем получать красные объекты.
Назначение
Паттерн предназначается для динамического подключения дополнительного поведения (состояния) к объекту. Благодаря этому шаблону, можно избежать создания кучи не нужных подклассов с целью расширения функциональности.
Расширение обычно подразумевает добавление кода перед, после или вместо основной функциональности методов объекта.
Хорошо спроектированный класс отвечает строго за определенные обязанности (high cohesion), декоратор позволяет легко наложить на него второстепенные задачи. Типичными второстепенными задачами могут быть: логирование, проверка полномочий, замер производительности, кеширование и т.п.
Недостатки паттерна:
- Чувствительность к порядку инициализации декораторов,
- Увеличение общей сложности и сложности при отладке.
Диаграмма
Базовый декоратор наследует тот же интерфейс что и компонент, благодаря чему клиент использующий декоратор может с ним работать как с компонентом. Конкретные декораторы добавляют дополнительное состояние (ConcreteDecoratorA) и дополнительное поведение (ConcreteDecoratorB), внутри переопределенных методов они вызывают методы внедрённого в них компонента (обычно внедрение происходит через конструктор, см. пример).
Пример
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
*&---------------------------------------------------------------------* *& Определение классов шаблона *&---------------------------------------------------------------------* CLASS lcx_error DEFINITION INHERITING FROM cx_static_check. PUBLIC SECTION. CONSTANTS: gc_no_data TYPE STRING VALUE `Нет данных`, gc_no_auth TYPE STRING VALUE `Нет полномочий`. METHODS: get_text REDEFINITION. CLASS-METHODS: raise IMPORTING iv_text TYPE string RAISING lcx_error. PRIVATE SECTION. DATA: mv_text TYPE STRING. ENDCLASS. CLASS lcx_error IMPLEMENTATION. METHOD raise. DATA: lo_exception TYPE REF TO lcx_error. CREATE OBJECT lo_exception. lo_exception->mv_text = iv_text. RAISE EXCEPTION lo_exception. ENDMETHOD. METHOD get_text. IF mv_text IS NOT INITIAL. result = mv_text. ELSE. result = super->get_text( ). ENDIF. ENDMETHOD. ENDCLASS. CLASS lcl_component DEFINITION ABSTRACT. PUBLIC SECTION. METHODS: get_data ABSTRACT RETURNING VALUE(rt_spfli) TYPE spfli_tab RAISING lcx_error. ENDCLASS. CLASS lcl_concrete_component DEFINITION INHERITING FROM lcl_component. PUBLIC SECTION. METHODS: get_data REDEFINITION. ENDCLASS. CLASS lcl_concrete_component IMPLEMENTATION. METHOD get_data. SELECT * FROM spfli INTO CORRESPONDING FIELDS OF TABLE rt_spfli. IF sy-subrc NE 0. lcx_error=>raise( iv_text = lcx_error=>gc_no_data ). ENDIF. ENDMETHOD. ENDCLASS. CLASS lcl_decorator DEFINITION ABSTRACT INHERITING FROM lcl_concrete_component. PUBLIC SECTION. METHODS: constructor IMPORTING io_component TYPE REF TO lcl_concrete_component. PROTECTED SECTION. DATA: mo_component TYPE REF TO lcl_concrete_component. ENDCLASS. CLASS lcl_decorator IMPLEMENTATION. METHOD constructor. super->constructor( ). mo_component = io_component. ENDMETHOD. ENDCLASS. CLASS lcl_log_decorator DEFINITION INHERITING FROM lcl_decorator. PUBLIC SECTION. METHODS: get_data REDEFINITION. ENDCLASS. CLASS lcl_log_decorator IMPLEMENTATION. METHOD get_data. WRITE: / 'Log before reading'. mo_component->get_data( ). WRITE: / 'Log after reading'. ENDMETHOD. ENDCLASS. CLASS lcl_auth_decorator DEFINITION INHERITING FROM lcl_decorator. PUBLIC SECTION. METHODS: get_data REDEFINITION. ENDCLASS. CLASS lcl_auth_decorator IMPLEMENTATION. METHOD get_data. WRITE: / 'Auth check'. mo_component->get_data( ). ENDMETHOD. ENDCLASS. *&---------------------------------------------------------------------* *& Работа с шаблоном *&---------------------------------------------------------------------* START-OF-SELECTION. DATA: lo_component TYPE REF TO lcl_concrete_component, lt_data TYPE SPFLI_TAB, lo_exc TYPE REF TO lcx_error. CREATE OBJECT lo_component. TRY. lo_component = NEW lcl_log_decorator( io_component = NEW lcl_auth_decorator( io_component = NEW lcl_concrete_component( ) ) ). lo_component->get_data( ). CATCH lcx_error INTO lo_exc. WRITE: / lo_exc->get_text( ). ENDTRY. |
В примере есть основной компонент lcl_concrete_component, который отвечает за чтение данных из БД, на этом его обязанности заканчиваются. Предположим, что нам понадобилось навесить на него обязанность вести журнал и проверку авторизации. Одним из решений является наследование от lcl_concrete_component и определение доп. логики в новом классе. Но что делать, если навешиваемое поведение необходимо определять динамически? Можно добавить параметры ведения логов и авторизации по параметру, но это не наш вариант, воспользуемся шаблоном декоратор.
Мы определяем базовый абстрактный класс декоратора, который является наследником компонента и содержит внутри себя ссылку на конкретный компонент. Далее создаём конкретных декораторов, которые внутри себя уже реализуют необходимое поведение: ведение логов и проверку полномочий.
Обратите внимание на последовательность создания декораторов и компонента, когда вы применяете этот паттерн, последовательность создания может иметь критичный характер и ошибившись вы можете получить неработоспособное решение.
ASTRAFOX, здравствуйте. Спасибо за интересный блог.
У меня возник вопрос по данной статье.
Возможно я чего-то не понимаю, но в вашей реализации класс декоратор наследуется напрямую от lcl_concrete_component, что не соответствует схеме.
При такой ситуации невозможно использование декоратора логирования и авторизации, например, для lcl_concrete_component1, lcl_concrete_component2, которые являются наследником lcl_component .
Действительно, наследовать нужно от lcl_component (как в схеме). Спасибо за исправление!
«Типичными второстепенными задачами могут быть: логирование, проверка полномочий, замер производительности, кеширование и т.п.» — эти задачи относятся, скорее, к реализации прокси, так как данные не изменяются по результатам выполнения метода-обертки.
У тебя в описании к прокси даже есть эта фраза: «Однако у них [прокси и декоратор] разные цели, заместитель в отличие от декоратора не должен изменять результаты операции реального объекта».
Как вариант примера: фильтрация данных, полученных из основного объекта, в зависимости от каких-либо хардкодных параметров или проверки полномочий (можно выдумать еще что-нибудь =D )
Хорошее объяснение разницы в применениях того и иного шаблона: http://stackoverflow.com/questions/18618779/differences-between-proxy-and-decorator-pattern
А какую роль играет базовый класс декоратора lcl_decorator?
Что мешает конкретные декораторы наследовать от компонента lcl_concrete_component?
Клиентский код и логика выполнения не поменяется.
Привет! Можно и так, однако конкретный компонент может иметь более широкий интерфейс, нежели тот, что нужен в декораторах. Тут скорее пример не совсем удачный, декорируемое поведение лучше вынести в отдельный интерфейс, реализуемый как компонентом, так и декораторами.
Привет:) Возможно, я не так выразил мысль. Я имел ввиду, какая ценность от класса Decorator, если можно декорировать поведение от Component и от него же наследовать конкретные декораторы? (см. первую UML диаграмму)
Так поведение Decorator не надо будет повторно описывать повторно в других Component. Но это возможно при изменении типа mo_component, например, на object.