Декоратор (шаблон проєктування)
Декоратор (фр. décorateur) — структурний шаблон проєктування, призначений для динамічного підключення додаткових можливостей до об'єкта. Шаблон Decorator надає гнучку альтернативу методу визначення підкласів з метою розширення функціональності.
Основні характеристики ред.
Завдання ред.
Об'єкт, який передбачається використовувати, виконує основні функції. Проте може виникнути потреба додати до нього деяку додаткову функціональність, яка виконуватиметься до і/або після основної функціональності об'єкта.
Спосіб вирішення ред.
Декоратор передбачає розширення функціональності об'єкта без визначення підкласів.
Учасники ред.
Клас ConcreteComponent
— клас, в який за допомогою шаблону Декоратор додається нова функціональність. В деяких випадках базова функціональність надається класами, похідними від класу ConcreteComponent
. У подібних випадках клас ConcreteComponent
є вже не абстрактним, а конкретним. Абстрактний клас Component
визначає інтерфейс для використання всіх цих класів.
Переваги ред.
- Декоратори забезпечують гнучку альтернативу підкласу для розширення функціональності
- Декоратори дозволяють модифікувати поведінку під час виконання, а не повертатися до існуючого коду та вносити зміни
- Декоратори - це хороше рішення для перестановки завдань, тому що ви можете загорнути компонент з будь-якою кількістю декораторів
- Шаблон декоратора підтримує принцип, що класи повинні бути відкриті для розширення, але закриті для модифікації
Недоліки ред.
- Декоратори можуть призвести до багатьох невеликих об'єктів у нашому дизайні, і надмірне використання може бути складним
- Декоратори можуть викликати проблеми, якщо клієнт сильно залежить від компонентів конкретного типу
- Декоратори можуть ускладнити процес аналізу компонента, оскільки вам потрібно не лише інвентувати компонент, але і обернути його кількома декораторами
- Може бути складно, щоб декоратори відслідковували інших декораторів, тому що повертатися назад до декількох шарів ланцюга декораторів починає натискати шаблон декоратора поза його справжнім наміром
Наслідки ред.
Функціональність, що додається, реалізується в невеликих об'єктах. Перевага полягає в можливості динамічно додавати цю функціональність до або після основної функціональності об'єкта ConcreteComponent
.
Зв'язок з іншими патернами ред.
- Стратегія змінює реалізацію, декоратор — доповнює
- Ланцюжок обов’язків та Декоратор виконують операції через серію пов’язаних об’єктів. Але Ланцюжок обов’язків може виконувати довільні дії, незалежні одна від одної, а також у будь-який момент переривати виконання, а декоратори розширюють певну дію, не ламаючи інтерфейс базової операції і не перериваючи виконання інших декораторів.
Реалізація ред.
Створюється абстрактний клас, що представляє як початковий клас, так і нові функції, що додаються в клас. У класах-декораторах нові функції викликаються в необхідній послідовності — до або після виклику подальшого об'єкта.
C++ ред.
#include <iostream>
using namespace std;
//абстрактний клас - основа патерну
//декорації підлягає метод do_it()
struct I
{
virtual ~I() {}
virtual void do_it() = 0;
};
//"справжній" клас - його метод do_it() мав працювати безумовно
struct A : public I
{
~A()
{
cout << "A dtor" << '\n';
}
virtual void do_it()
{
cout << 'A';
}
};
//ще один абстрактний клас - основа майбутніх декорацій-обгорток
class D : public I
{
private:
I* m_wrappee;
public:
//декоратор приховує всередині обгорнутий "справжній" об'єкт
//і перенаправляє йому запити щось зробити
D(I* inner) : m_wrappee(inner) {}
~D()
{
delete m_wrappee;
}
virtual void do_it()
{
m_wrappee->do_it();
}
};
// конкретні реалізації обгорток: спочатку працює вкладений об'єкт, потім - обгортка
struct X : public D
{
X(I* core) : D(core) {}
~X()
{
cout << "X dtor" << " ";
}
virtual void do_it()
{
D::do_it();
cout << 'X';
}
};
struct Y : public D
{
Y(I* core) : D(core) {}
~Y()
{
cout << "Y dtor" << " ";
}
virtual void do_it()
{
D::do_it();
cout << 'Y';
}
};
struct Z : public D
{
Z(I* core) : D(core) {}
~Z()
{
cout << "Z dtor" << " ";
}
virtual void do_it()
{
D::do_it();
cout << 'Z';
}
};
void main()
{
I* anXYZ = new Z(new Y(new X(new A)));
anXYZ->do_it(); cout << '\n'; // A X Y Z
delete anXYZ;
A a;
I* anX = new X(&a);
anX->do_it(); cout << '\n'; // A X
}
C# ред.
using System;
namespace Decorator
{
// основа патерну
interface INotifier
{
void Send();
}
// "справжній" клас
// надсилає сповіщення користувачу
class UserNotifier : INotifier
{
public void Send()
{
Console.WriteLine("Notify user regularly");
}
}
// абстрактний клас декорацій
abstract class NotifierDecoratorBase : INotifier
{
// декоратор приховує всередині обгорнутий "справжній" об'єкт
protected INotifier notifier;
public NotifierDecoratorBase(INotifier notifier)
{
this.notifier = notifier;
}
// перенаправляє "справжньому" об'єкту запити щось зробити
public virtual void Send()
{
notifier.Send();
}
}
// різноманітні декорації
// додаткові алгоритми надсилання сповіщень
class SmsNotifier : NotifierDecoratorBase
{
public SmsNotifier(INotifier notifier)
: base(notifier) { }
public override void Send()
{
// спочатку працює вкладений об'єкт, потім - обгортка
base.Send();
Console.WriteLine("Notify user with sms");
}
}
class EmailNotifier : NotifierDecoratorBase
{
public EmailNotifier(INotifier notifier)
: base(notifier) { }
public override void Send()
{
base.Send();
Console.WriteLine("Notify user with email");
}
}
class Program
{
// конфігурації системи
static bool isSmsNotificationEnabled = true;
static bool isEmailNotificationEnabled = false;
static void Main(string[] args)
{
// створюємо спосіб оповіщення
INotifier notifier = new UserNotifier();
// обгортаємо його залежно від налаштувань систему
if (isSmsNotificationEnabled) notifier = new SmsNotifier(notifier);
if (isEmailNotificationEnabled) notifier = new EmailNotifier(notifier);
// виконуємо дію
notifier.Send();
Console.ReadLine();
}
}
}
Java ред.
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
abstract class WordSplitter {
abstract public List<String> getWords(String wordString);
}
/**
Просто перетворює рядок слів, написаних через пробіл, на список слів
*/
class PureWordSplitter extends WordSplitter {
public PureWordSplitter() {}
public List<String> getWords(String wordString) {
String[] wordArray = wordString.split(" ");
List<String> wordList = new ArrayList<String>(wordArray.length);
Collections.addAll(wordList, wordArray);
return wordList;
}
}
/**
Абстрактний клас для декораторів
*/
abstract class FilteringSplitter extends WordSplitter {
protected WordSplitter decoratedWordSplitter;
abstract public List<String> getWords(String wordString);
}
/**
Декоратор, який видаляє слова з не більше ніж двома буквами
*/
class ShortWordsFilteringSplitter extends FilteringSplitter {
public ShortWordsFilteringSplitter(WordSplitter wordSplitter) {
decoratedWordSplitter = wordSplitter;
}
public List<String> getWords(String wordString) {
List<String> wordList = decoratedWordSplitter.getWords(wordString);
for (int i = 0; i < wordList.size();) {
if (wordList.get(i).length() <= 2) {
wordList.remove(i);
} else {
++i;
}
}
return wordList;
}
}
/**
Декоратор, який видаляє зі списку слова, що починаються з великої букви
*/
class PropperNamesFilteringSplitter extends FilteringSplitter {
public PropperNamesFilteringSplitter(WordSplitter wordSplitter) {
decoratedWordSplitter = wordSplitter;
}
public List<String> getWords(String wordString) {
List<String> wordList = decoratedWordSplitter.getWords(wordString);
for (int i = 0; i < wordList.size();) {
String word = wordList.get(i);
if (word.isEmpty() || Character.isUpperCase(word.charAt(0))) {
wordList.remove(i);
} else {
++i;
}
}
return wordList;
}
}
class Main {
public static void main(String[] args) {
WordSplitter wordSplitter = new PropperNamesFilteringSplitter(new ShortWordsFilteringSplitter(new PureWordSplitter()));
List<String> result = wordSplitter.getWords("no hi Afrika yes ambitious come Ukraine Ua");
for (String word : result) {
System.out.print(word + " ");
}
}
}
Зауваження і коментарі ред.
- Хоча об'єкт-декоратор може додавати свою функціональність до або після функціональності основного об'єкта, ланцюжок створюваних об'єктів завжди повинен закінчуватися об'єктом класу
ConcreteComponent
. - Базові класи мови Java широко використовують шаблон Декоратор для організації обробки операцій введення-виведення.
Посилання ред.
- Decorator design pattern [Архівовано 14 жовтня 2007 у Wayback Machine.]
- Design Patterns: Elements of Reusable Object-Oriented Software [Архівовано 9 листопада 2012 у Wayback Machine.]
- Декоратор (Decorator) - Дизайн-патерни — просто, як двері
- Чим відрізняється декоратор від адаптера? (І про фасад) [Архівовано 22 грудня 2014 у Wayback Machine.] - Блог одного кібера
Література ред.
Алан Шаллоуей, Джеймс Р. Тротт. Шаблоны проектирования. Новый подход к объектно-ориентированному анализу и проектированию = Design Patterns Explained: A New Perspective on Object-Oriented Design. — М. : «Вильямс», 2002. — 288 с. — ISBN 0-201-71594-5.