Pull to refresh

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

Reading time 4 min
Views 88K
Почитать описание других паттернов.

Проблема


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

Описание


Для более детального понимания проблемы, рассмотрим конкретную ситуацию. Пусть имеется некоторый объект — «кнопка», принадлежащий классу объектов «Кнопка», на который понадобилось возложить дополнительные обязанности. Под обязанностями, в данном контексте, понимаются какие-либо особенности поведения объекта. В случае с кнопкой, можно рассмотреть поведение объекта при его отображении на экране. При этом, будем считать, дополнительными обязанностями — отображение рамки кнопки, надписи, иконки. Важно понимать, что все эти обязанности должны иметь возможность быть наложенными как одновременно, так и по отдельности. Очевидно, первое, что приходит на ум — порождение классов (механизм наследования). Для данной задачи возможно это и выход — расширить класс «Кнопка» семью (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».
Tags:
Hubs:
+35
Comments 18
Comments Comments 18

Articles