Паттерн Decorator (декоратор, wrapper, обертка)
Назначение паттерна Decorator
- Паттерн Decorator динамически добавляет новые обязанности объекту. Декораторы являются гибкой альтернативой порождению подклассов для расширения функциональности.
- Рекурсивно декорирует основной объект.
- Паттерн Decorator использует схему "обертываем подарок, кладем его в коробку, обертываем коробку".
Решаемая проблема
Вы хотите добавить новые обязанности в поведении или состоянии отдельных объектов во время выполнения программы. Использование наследования не представляется возможным, поскольку это решение статическое и распространяется целиком на весь класс.
Обсуждение паттерна Decorator
Предположим, вы работаете над библиотекой для построения графических пользовательских интерфейсов и хотите иметь возможность добавлять в окно рамку и полосу прокрутки. Тогда вы могли бы определить иерархию наследования следующим образом...
UML-диаграмма классов паттерна Decorator
Эта схема имеет существенный недостаток - число классов сильно разрастается.
Паттерн Decorator дает клиенту возможность задавать любые комбинации желаемых "особенностей".
Widget* aWidget = new BorderDecorator(
new HorizontalScrollBarDecorator(
new VerticalScrollBarDecorator(
new Window( 80, 24 ))));
aWidget->draw();
Гибкость может быть достигнута следующим дизайном.
Другой пример каскадного соединения свойств (в цепочку) для придания объекту нужных характеристик…
Stream* aStream = new CompressingStream(
new ASCII7Stream(
new FileStream( "fileName.dat" )));
aStream->putString( "Hello world" );
Решение этого класса задач предполагает инкапсуляцию исходного объекта в абстрактный интерфейс. Как объекты-декораторы, так и основной объект наследуют от этого абстрактного интерфейса. Интерфейс использует рекурсивную композицию для добавления к основному объекту неограниченного количества "слоев" - декораторов.
Обратите внимание, паттерн Decorator позволяет добавлять объекту новые обязанности, не изменяя его интерфейс (новые методы не добавляются). Известный клиенту интерфейс должен оставаться постоянным на всех, следующих друг за другом "слоях".
Отметим также, что основной объект теперь "скрыт" внутри объекта-декоратора. Доступ к основному объекту теперь проблематичен.
Структура паттерна Decorator
Клиент всегда заинтересован в функциональности CoreFunctionality.doThis()
. Клиент может или не может быть заинтересован в методах OptionalOne.doThis()
и OptionalTwo.doThis()
. Каждый из этих классов переадресует запрос базовому классу Decorator
, а тот направляет его в декорируемый объект.
Пример паттерна Decorator
Паттерн Decorator динамически добавляет новые обязанности объекту. Украшения для новогодней елки являются примерами декораторов. Огни, гирлянды, игрушки и т.д. вешают на елку для придания ей праздничного вида. Украшения не меняют саму елку, а только делают ее новогодней.
Хотя картины можно повесить на стену и без рамок, рамки часто добавляются для придания нового стиля.
Использование паттерна Decorator
- Подготовьте исходные данные: один основной компонент и несколько дополнительных (необязательных) "оберток".
- Создайте общий для всех классов интерфейс по принципу "наименьшего общего знаменателя НОЗ" (lowest common denominator LCD). Этот интерфейс должен делать все классы взаимозаменяемыми.
- Создайте базовый класс второго уровня (
Decorator
) для поддержки дополнительных декорирующих классов. - Основной класс и класс
Decorator
наследуют общий НОЗ-интерфейс. - Класс
Decorator
использует отношение композиции. Указатель на НОЗ-объект инициализируется в конструкторе. - Класс
Decorator
делегирует выполнение операции НОЗ-объекту. - Для реализации каждой дополнительной функциональности создайте класс, производный от
Decorator
. - Подкласс
Decorator
реализует дополнительную функциональность и делегирует выполнение операции базовому классуDecorator
. - Клиент несет ответственность за конфигурирование системы: устанавливает типы и последовательность использования основного объекта и декораторов.
Особенности паттерна Decorator
- Adapter придает своему объекту новый интерфейс, Proxy предоставляет тот же интерфейс, а Decorator обеспечивает расширенный интерфейс.
- Adapter изменяет интерфейс объекта. Decorator расширяет ответственность объекта. Decorator, таким образом, более прозрачен для клиента. Как следствие, Decorator поддерживает рекурсивную композицию, что невозможно с чистыми адаптерами.
- Decorator можно рассматривать как вырожденный случай Composite с единственным компонентом. Однако Decorator добавляет новые обязанности и не предназначен для агрегирования объектов.
- Decorator позволяет добавлять новые функции к объектам без наследования. Composite фокусирует внимание на представлении, а не декорировании. Эти характеристики являются различными, но взаимодополняющими, поэтому Composite и Decorator часто используются вместе.
- Decorator и Proxy имеют разное назначение, но схожие структуры. Их реализации хранят ссылку на объект, которому они отправляют запросы.
- Decorator позволяет изменить внешний облик объекта, Strategy – его внутреннее содержание.
Реализация паттерна Decorator
Паттерн Decorator: до и после
До
Используется следующая иерархия наследования:
class A {
public:
virtual void do_it() {
cout << 'A';
}
};
class AwithX: public A {
public:
/*virtual*/
void do_it() {
A::do_it();
do_X();
};
private:
void do_X() {
cout << 'X';
}
};
class AwithY: public A {
public:
/*virtual*/
void do_it() {
A::do_it();
do_Y();
}
protected:
void do_Y() {
cout << 'Y';
}
};
class AwithZ: public A {
public:
/*virtual*/
void do_it() {
A::do_it();
do_Z();
}
protected:
void do_Z() {
cout << 'Z';
}
};
class AwithXY: public AwithX, public AwithY
{
public:
/*virtual*/
void do_it() {
AwithX::do_it();
AwithY::do_Y();
}
};
class AwithXYZ: public AwithX, public AwithY, public AwithZ
{
public:
/*virtual*/
void do_it() {
AwithX::do_it();
AwithY::do_Y();
AwithZ::do_Z();
}
};
int main() {
AwithX anX;
AwithXY anXY;
AwithXYZ anXYZ;
anX.do_it();
cout << '\n';
anXY.do_it();
cout << '\n';
anXYZ.do_it();
cout << '\n';
}
Вывод программы:
AX
AXY
AXYZ
После
Заменим наследование делегированием.
Обсуждение. Используйте агрегирование вместо наследования для декорирования "основного" объекта. Тогда клиент сможет динамически добавлять новые обязанности объектам, в то время как архитектура, основанная на множественном наследовании, является статичной.
class I {
public:
virtual ~I(){}
virtual void do_it() = 0;
};
class A: public I {
public:
~A() {
cout << "A dtor" << '\n';
}
/*virtual*/
void do_it() {
cout << 'A';
}
};
class D: public I {
public:
D(I *inner) {
m_wrappee = inner;
}
~D() {
delete m_wrappee;
}
/*virtual*/
void do_it() {
m_wrappee->do_it();
}
private:
I *m_wrappee;
};
class X: public D {
public:
X(I *core): D(core){}
~X() {
cout << "X dtor" << " ";
}
/*virtual*/
void do_it() {
D::do_it();
cout << 'X';
}
};
class Y: public D {
public:
Y(I *core): D(core){}
~Y() {
cout << "Y dtor" << " ";
}
/*virtual*/
void do_it() {
D::do_it();
cout << 'Y';
}
};
class Z: public D {
public:
Z(I *core): D(core){}
~Z() {
cout << "Z dtor" << " ";
}
/*virtual*/
void do_it() {
D::do_it();
cout << 'Z';
}
};
int main() {
I *anX = new X(new A);
I *anXY = new Y(new X(new A));
I *anXYZ = new Z(new Y(new X(new A)));
anX->do_it();
cout << '\n';
anXY->do_it();
cout << '\n';
anXYZ->do_it();
cout << '\n';
delete anX;
delete anXY;
delete anXYZ;
}
Вывод программы:
AX
AXY
AXYZ
X dtor A dtor
Y dtor X dtor A dtor
Z dtor Y dtor X dtor A dtor
Паттерн проектирования Decorator по шагам
- Создайте "наименьший общий знаменатель", делающий классы взаимозаменяемыми.
- Создайте базовый класс второго уровня для реализации дополнительной функциональности.
- Основной класс и класс-декоратор используют отношение "является".
- Класс-декоратор "имеет" экземпляр "наименьшего общего знаменателя".
- Класс
Decorator
делегирует выполнение операции объекту "имеет". - Для реализации каждой дополнительной функциональности создайте подклассы
Decorator
. - Подклассы
Decorator
делегируют выполнение операции базовому классу и реализуют дополнительную функциональность. - Клиент несет ответственность за конфигурирование нужной функциональности.
#include <iostream>
using namespace std;
// 1. " Наименьший общий знаменатель"
class Widget
{
public:
virtual void draw() = 0;
};
// 3. Основной класс, использующий отношение "является"
class TextField: public Widget
{
int width, height;
public:
TextField(int w, int h)
{
width = w;
height = h;
}
/*virtual*/
void draw()
{
cout << "TextField: " << width << ", " << height << '\n';
}
};
// 2. Базовый класс второго уровня
class Decorator: public Widget // 3. использует отношение "является"
{
Widget *wid; // 4. отношение "имеет"
public:
Decorator(Widget *w)
{
wid = w;
}
/*virtual*/
void draw()
{
wid->draw(); // 5. делегирование
}
};
// 6. Дополнительное декорирование
class BorderDecorator: public Decorator
{
public:
BorderDecorator(Widget *w): Decorator(w){}
/*virtual*/
void draw()
{
// 7. Делегирование базовому классу и
Decorator::draw();
// 7. реализация дополнительной функциональности
cout << " BorderDecorator" << '\n';
}
};
// 6. Дополнительное декорирование
class ScrollDecorator: public Decorator
{
public:
ScrollDecorator(Widget *w): Decorator(w){}
/*virtual*/
void draw()
{
// 7. Delegate to base class and add extra stuff
Decorator::draw();
cout << " ScrollDecorator" << '\n';
}
};
int main()
{
// 8. Клиент ответственен за конфигурирование нужной функциональности
Widget *aWidget = new BorderDecorator(
new BorderDecorator(
new ScrollDecorator
(new TextField(80, 24))));
aWidget->draw();
}
TextField: 80, 24
ScrollDecorator
BorderDecorator
BorderDecorator
Источник: http://sourcemaking.com/design_patterns/decorator/