Паттерн проектирования «Декоратор» / «Decorator»

    Почитать описание других паттернов.

    Проблема


    Возложить дополнительные обязанности (прозрачные для клиентов) на отдельный объект, а не на класс в целом.

    Описание


    Для более детального понимания проблемы, рассмотрим конкретную ситуацию. Пусть имеется некоторый объект — «кнопка», принадлежащий классу объектов «Кнопка», на который понадобилось возложить дополнительные обязанности. Под обязанностями, в данном контексте, понимаются какие-либо особенности поведения объекта. В случае с кнопкой, можно рассмотреть поведение объекта при его отображении на экране. При этом, будем считать, дополнительными обязанностями — отображение рамки кнопки, надписи, иконки. Важно понимать, что все эти обязанности должны иметь возможность быть наложенными как одновременно, так и по отдельности. Очевидно, первое, что приходит на ум — порождение классов (механизм наследования). Для данной задачи возможно это и выход — расширить класс «Кнопка» семью (23-1 = 7) различными классами, сочетающими в себе всевозможные комбинации обязанностей. Это классы: «Кнопка_С_Надписью», «Кнопка_С_Рамкой», «Кнопка_С_Иконкой», «Кнопка_С_Надписью_И_Иконкой», «Кнопка_С Рамкой_И_Иконкой», «Кнопка_С_Надписью_И_Рамкой», «Кнопка_С_Надписью_И_Рамкой_И_Иконкой». А если таких обязанностей будет не три, а хотя бы десять, не говоря уже про неудобство работы с подобной структурой. Безусловно, порождение классов в таком случае — заведомо проигрышный вариант. Однако, из этой ситуации есть выход — паттерн «Декоратор».

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

    Практическая задача


    Используя паттерн «Декоратор», реализуем каркас редактора блок-схем. Будем использовать «Декоратор» для наложения особенностей отрисовки отдельных элементов схем. Рассмотрим два типа блоков — терминальный блок (начало/конец) и блок процессов (описывает одно или несколько действий) и будем их декорировать рамкой и надписью.

    Диаграмма классов


    Рассмотрим диаграмму классов. AbstractBlock — интерфейс любого блока блок-схемы, имеющий единственный метод — draw(), вызываемый клиентом. Является одновременно и интерфейсом декорируемого объекта и интерфейсом декоратора. TerminatorBlock и ProcessBlock — уточнения абстрактного блока. AbstractBlockDectorator — абстрактный класс декоратора блоков. Обратите внимание, что это именно абстрактный класс, а не интерфейс. Дело в том, что AbstractBlockDecorator, по умолчанию, в методе draw() делегирует соответствующий метод декорируемого объекта. LabelBlockDecorator и BorderBlockDecorator — уточнения декораторов блока, в качестве декоратора меток и рамок. Данные классы переопределяют метод draw() базового класса, добавляя декорируемому объекту новые обязанности (методы drawLabel(), drawBorder()).



    Реализация на Python


    По поводу реализации, хотелось бы сделать небольшое замечание. Ввиду того, что Python не поддерживает описание интерфейсов, рекомендуется описывать их как классы, но с обязательным напоминанием программисту, что это именно интерфейс а не абстрактный класс. Напоминание можно сделать в виде исключения NotImplementedError().

    # -*- coding: cp1251 -*-
    class AbstractBlock:
      """ Абстрактный блок
      "
    ""
      def draw(self):
        raise NotImplementedError();

    class TerminatorBlock(AbstractBlock):
      """ Терминальный блок (начало/конец, вход/выход)
      "
    ""
      def draw(self):
        print "Terminator block drawing ... "

    class ProcessBlock(AbstractBlock):
      """ Блок - процесс (один или несколько операторов)  
      "
    ""
      def draw(self):
        print "Process block drawing ... "

    class AbstractBlockDecorator(AbstractBlock):
      """ Абстракный декоратор блоков
      "
    ""
      def __init__(self, decoratee):
        # _decoratee - ссылка на декорируемый объект
        self._decoratee = decoratee
      
      def draw(self):
        self._decoratee.draw()

    class LabelBlockDecorator(AbstractBlockDecorator):
      """ Декорирует блок текстовой меткой
      "
    ""
      def __init__(self, decoratee, label):
        self._decoratee = decoratee
        self._label = label
      
      def draw(self):
        AbstractBlockDecorator.draw(self)
        self._drawLabel()
      
      def _drawLabel(self):
        print " ... drawing label " + self._label

    class BorderBlockDecorator(AbstractBlockDecorator):
      """ Декорирует блок специальной рамкой
      "
    ""
      def __init__(self, decoratee, borderWidth):
        self._decoratee = decoratee
        self._borderWidth = borderWidth
      
      def draw(self):
        AbstractBlockDecorator.draw(self)
        self._drawBorder()
      
      def _drawBorder(self):
        print " ... drawing border with width " + str(self._borderWidth)

    # терминальный блок
    tBlock = TerminatorBlock()
    # блок - процесс
    pBlock = ProcessBlock()

    # Применим LabelDecorator к терминальному блоку
    labelDecorator = LabelBlockDecorator(tBlock, "Label222")

    # Применим BorderDecorator к терминальному блоку, после применения LabelDecorator
    borderDecorator1 = BorderBlockDecorator(labelDecorator, 22)

    # Применим BorderDecorator к блоку - процессу
    borderDecorator2 = BorderBlockDecorator(pBlock, 22)

    labelDecorator.draw()
    borderDecorator1.draw()
    borderDecorator2.draw()

    * This source code was highlighted with Source Code Highlighter.

    Кто-то, наконец-то дождался примеров на питоне.

    Примечание


    Паттерн «Декоратор» так-же известен под названием «Обертка»/«Wrapper».
    Share post

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 18

      –1
      А я бы не стал так кнопки делать, сделал бы лучше как в WPF, хоть это и заняло бы больше времени. На С++ и того проще, все решается множественным наследованием и стратегиями поведения.
        +3
        Вы не решите эту проблему множественным наследованием. Про это я писал в описании. Точнее решите, породив кучу классов. При этом, если понадобится какое-либо изменение одного декоратора, вам понадобится менять все классы. А декоратором может быть даже и не десять.

        В случае применения паттерна «Декоратор» Вам нужно будет лишь изменить один класс.

        И да. Питон поддерживает множественное наследование. Поэтому язык тут не причем.
          +3
          На счёт WPF. Именно в нём паттерн декоратор используется очень плотно, и очень многие встроенные возможности WPF сделаны как паттерн декоратор. В частности в механизм Addorner-ов.

          Автору спасибо. Хоть и знаком с предметом, но всё равно интересно!

          +2
          не три факториал, а два в кубе (если не считать оригинал, то два в кубе минус единица)
            +1
            Да. Вы правы. Комбинаторика блин :) Спасибо за замечание.
            +1
            «Два в кубе — 1» равно 7.
            Вы пропустили класс «Кнопка_С_Надписью_И_Рамкой» :-)
              0
              :) Самое интересное, что народ молчит :) 2 в кубе — 1 = шесть. Конечно, вы правы.
              +2
              Отдельное спасибо за пример на Python! — обычно мало где их приводят.

              И отдельное за хорошую статью!
                +1
                В английской википедии есть примеры на python практически ко всем паттернам GOF. Советую.
                Сам по ним разбирался после прочтения книги.
                0
                Еще неплохо бы вспомнить что одно из распространенных названий данного шаблона Wraper, что на русском звучит как «Обертка».
                  0
                  Спасибо. Добавил в пост.
                  0
                  CSS (: Используется не только в HTML, но и во Flex.
                    0
                    Я в примере на питоне, не понял только одного:

                    Зачем AbstractBlockDecorator наследуется от AbstractBlock?
                      0
                      Это и есть основная идея паттерна — сделать наложения декоратора абсолютно прозрачным для клиента.
                      0
                      Еще одно замечаение если вы заговорили о питоне. В нем на уровне синтаксиса введено понятие декоратора функций www.python.org/dev/peps/pep-0318/ и классов www.python.org/dev/peps/pep-3129/ и важно не запутаться в терминологии.
                        0
                        Ух ты спасибо. Не знал. Но Вы должны понимать, что «говорил» я о питоне, только в рамках реализации конкретного примера :)
                        –1
                        объясни мне зачем в имени интерфейса использовать слово Abstract, а потом писать:
                        >AbstractBlockDectorator — абстрактный класс декоратора блоков. Обратите внимание, что это именно абстрактный
                        >класс, а не интерфейс.
                        не стОит запутывать людей.
                          0
                          Замечательная статья. Спасибо. Как раз разбиралась с этим шаблоном. Теперь буду реализовывать! Надеюсь, что получиться. На первый взгляд, вроде, все не так уж и сложно..)

                          Only users with full accounts can post comments. Log in, please.