Декоратор (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.

 Декоратор. Призначення. Приєднує додаткові обов’язки до об’єкта динамічно. Декорування надає гнучку альтернативу наслідуванню в питання розширення функціональності.

[2] http://c2.com/cgi/wiki?DecoratorPattern