Декоратор (Decorator)
Розкажу я вам про лікаря, який мав хорошу й швидку машину mercedes. Оскільки він лікар львівської лікарні, то по дорозі на роботу він часто застрягає у пробках, тому він спізнюється і це його виводить із себе, в результаті чого страждають... Хто? - Його пацієнти. В нашого лікаря є мрія, що його машина перетвориться на карету швидкої допомоги і всі інші автомобілі уступатимуть йому дорогу на роботу. Тільки одна проблема із цим: швидка вміє сигналити сиреною, а його мерс не має такої можливості. Нам, як програмістам, було б добре декорувати (decorate) «мерс», таким чином, щоб він почав голосно «вити». Як ми це можемо зробити?Декоратор використовується для надання деякої додаткової функціональності нашим об'єктам.[1]
В нашому прикладі, ми хочемо додати функціональність сирени до конкретної імплементації автомобіля, проте нам нічого не заважатиме причепити на машину газовий розпилювач абощо. Отже, маємо базовий клас автомобіля (Car):
Уривок коду 9.1. Базовий клас Автомобіля
class Car
{
protected String BrandName { get; set; }
public virtual void Go()
{
Console.WriteLine("I'm " + BrandName + " and I'm on my way...");
}
}
// Конкретна реалізація класу Car
class Mercedes : Car
{
public Mercedes()
{
BrandName = "Mercedes";
}
}
Для того щоб зберегти контракт базового класу Car, і щоб мати базовий клас для всіх інших «прибамбасних» функціональностей створимо CarDecorator, що так само наслідується від Car. Цей клас і буде основою для патерну Декоратор:
Уривок коду 9.2. Декоратор ззовні той же автомобіль, але агрегує справжній всередині
class DecoratorCar : Car
{
protected Car DecoratedCar { get; set; }
public DecoratorCar(Car decoratedCar)
{
DecoratedCar = decoratedCar;
}
public override void Go()
{
DecoratedCar.Go();
}
}
Як ми уже зауважили цей клас агрегує справжній автомобіль, або іншими словами обгортає DecoratedCar. Це є причина чому цей патерн ще часто називають Обгорткою (або Wrapper). Можливо ви десь зустрічали цю ж назву і для дизайн-патерну Адаптер, його так само могли називати «враппер». Обидва патерни можуть так називати, але слід розрізняти їхнє призначення. Патерн Адаптер застосовується для представлення одного під виглядом іншого, а Декоратор для додавання до іншуючого більше функціональності, хоч вони обоє агрегують певний об’єкт.[2]
Настав час для «прибамбасів». Ми додаємо додаткову функціональність до будь-якої машини (чи то «мерс» чи то «беха»), наслідуючися від CarDecorator класу. Тут ми додали простий екстеншин біпкання.
Уривок коду 9.3. «декор» карети швидкої допомоги
class AmbulanceCar : DecoratorCar
{
public AmbulanceCar(Car decoratedCar)
: base(decoratedCar)
{
}
public override void Go()
{
base.Go();
Console.WriteLine("... beep-beep-beeeeeep ...");
}
}
І власне те чого ми добивалися
Викорисання вигладає дуже приємно - ми покриваємо мерседес «фічами» швидкої і отримуємо мрію лікаря (змінна doctorsDream), опісля ми можемо покрити цей об’єкт ще якимись штукенціми, і що важливо ми це все можемо робити динамічно. Сниться лікарю біпкалка – передаємо його мерс у Декоратор AmbulanceCar, а сниться йому кулемет на криші автомобіля – передаємо у декоратор ArmorCar.
Уривок коду 9.4. Використання патерну
var doctorsDream = new AmbulanceCar(new Mercedes());
doctorsDream.Go();
Вивід:
I'm Mercedes and I'm on my way...
... beep-beep-beeeeeep ...
Надіюся що цей вивід був очікуваний. А тепер швидко глянемо на UML цієї мудрості-чудасії:
UML-діаграма 6. Декоратор
Цей патерн має щось спільне із Компонувальником і Адаптером. Адаптер може змінити інтерфейс поведінки, а Декоратор ні (ми наслідуємося від Car). Компонувальник працює із великою кількістю компонент, а не як Декоратор тільки із однією.
[1] Decorator. Intent. Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
Декоратор. Призначення. Приєднує додаткові обов’язки до об’єкта динамічно. Декорування надає гнучку альтернативу наслідуванню в питання розширення функціональності.