Декоратор (шаблон проєктування)
Декоратор, Decorator — структурний шаблон проєктування, призначений для динамічного підключення додаткових можливостей до об'єкта. Шаблон 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
- Design Patterns: Elements of Reusable Object-Oriented Software
- Декоратор (Decorator) - Дизайн-патерни — просто, як двері
- Чим відрізняється декоратор від адаптера? (І про фасад) - Блог одного кібера
Література
Алан Шаллоуей, Джеймс Р. Тротт. Шаблоны проектирования. Новый подход к объектно-ориентированному анализу и проектированию = Design Patterns Explained: A New Perspective on Object-Oriented Design. — М. : «Вильямс», 2002. — 288 с. — ISBN 0-201-71594-5.