“Патерн Вівторка” #2: Декоратор

Розкажу я вам про лікаря, який мав хорошу й швидку машину – Mersedes. Оскільки він лікар Львівської лікарні, то по дорозі на роботу він часто застрягає у пробках, тому він спізнюється і це його виводить із себе, в результаті чого страждають… Хто? – Його пацієнти. В нашого лікаря є мрія, що його машина перетворилася на Швидку і всі інші автомобілі уступаються йому дорогу на роботу. Тільки одна проблема із цим: Швидка вміє сигналити сиреною, а його мерс не має такої можливості, нам, як програмістам, було б добре ДЕКОРУВАТИ (decorate) мерс, таким чином, щоб він почав голосно “вити”.
Як ми це можемо зробити?


ДЕКОРАТОР

Отже, маємо базовий клас Автомобіля (Car):

    public class Car
    {
        protected String BrandName { get; set; }
        public virtual void Go()
        {
            Console.WriteLine(“I’m “ + BrandName + ” and I’m on my way…”);
        }
    }

і конкретна імплементація – Mersedes:

    public class Mersedes : Car
    {
        public Mersedes()
        {
            BrandName = “Mersedes”;
        }
    }

Паттерн Декоратор використовується для надання деякої додаткової функціональності нашим об’єктам. В нашому прикладі, ми хочемо додати функціональність сирени до конкретної імплементації автомобіля, проте нам нічого не заважає причепити на машину газовий розпилювач абощо. Для того щоб зберегти контракт базового класу Car, і щоб мати базовий клас для всіх інших “прибамбасних” функціональностей створимо CarDecorator, що так само наслідується від Car:

    public class DecoratorCar : Car
    {
        protected Car DecoratedCar { get; set; }
        public DecoratorCar(Car decoratedCar)
        {
            DecoratedCar = decoratedCar;
        }
        public override void Go()
        {
            DecoratedCar.Go();
        }
    }

Як ми уже зауважили цей клас обгортує DecoratedCar. Це є причина чому цей патерн ще часто називають Wrapper.

Настав час для “прибамбасів”. Ми додаємо додаткову функціональність до будь-якої машини (чи то мерс чи то беха), наслідуючися від CarDecorator класу.

    public class AmbulanceCar : DecoratorCar
    {
        public AmbulanceCar(Car decoratedCar)
            : base(decoratedCar)
        {
        }
        public override void Go()
        {
            base.Go();
            Console.WriteLine(“… beep-beep-beeeeeep …”);
        }
    }

Тут ми додали простий екстеншин біпкання.

І власне те чого ми добивалися:

Викорисання вигладає дуже приємно – ми покриваємо Мерседс фічами Швидкої і отримуємо мрію лікаря (doctorsDream), опісля ми можемо покрити цей об’єкт ще єкимись штукенціми, і що важливо ми це все можемо робити динамічно. Сниться лікарю* біпкалка – передаємо його мерс у декоратор Ambulance, сниться йому кулемет на криші автомобіля, передаємо у декоратор Armor.

            var doctorsDream = new AmbulanceCar(new Mersedes());
            doctorsDream.Go();

Вивід:

I’m Mersedes and I’m on my way…
… beep-beep-beeeeeep …

Надіюся що цей аутпут був очікуваний. А тепер швидко глянемо на UML цієї мудрості-чудасії:

Цей патерн має щось спільне із Композитом (Composite) і Адаптером (Adapter). Адаптер може змінити інтерфейс поведінки, а Декоратор ні (ми наслідуємося від Car). Композит працює із великою кількістю компонент, а не як декоратор тільки із однією.

Якщо вам сподобався виклад цього дизайн патерну, дайте мені знати.
Також Ви можете глянути на табличку із ДП на блозі автора тут.

(*) раніше було “сниться дікарю біпкалка”, стало “сниться лікарю біпкалака”.

12 коментарів

Залишити коментар до Petro Скасувати коментар

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *