Decorator

Przeznaczenie

  • Pozwala na dynamiczne dołączanie do obiektów dodatkowych zobowiązań

  • Zapewnia elastyczną alternatywę dla tworzenia podklas w celu rozszerzania funkcjonalności

Kontekst

  • Część aplikacji wymaga dynamicznej zmiany funkcjonalności

Problem

  • Chcemy dynamicznie i w przezroczysty sposób (tzn. nie wpływający na inne obiekty) dodać zobowiązania do pojedynczych obiektów

  • Dodawane zobowiązania mogą być cofnięte

Gdy rozszerzanie funkcjonalności przez definiowanie podklas jest niepraktyczne – czasami jest możliwych wiele niezależnych rozszerzeń, które przy próbie uwzględnienia każdej z ich kombinacji prowadzą do gwałtownego wzrostu liczby klas.

Scenariusz

Chcemy rozszerzyć możliwości obiektu Photo i dodać do zdjęcia ramkę oraz dwa opisy (tagi):

_images/coffee.png

Rozwiązanie – dwie alternatywy:

Dodanie nowego zobowiązania przez zastosowanie dziedziczenia:

  • rozwiązanie mało elastyczne (statyczne)

_images/Decorator_1.png

Umieszczenie komponentu w innym obiekcie, który dodaje ramkę:

  • obiekt będący otoczką komponentu nazywa się dekoratorem

  • dekorator dostosowuje się do interfejsu ozdabianego obiektu, dzięki czemu staje się przezroczysty dla klientów

  • dekorator przekazuje żądania do komponentu i może wykonywać dodatkowe akcje

_images/Decorator_2.png

Wzorzec dekoratora umożliwia składanie obiektu Photo z dekoratorami BorderedPhoto i TaggedPhoto.

_images/Decorator_3.png

Dziedziczenie jest jedną z form rozszerzenia funkcjonalności klasy, ale niekoniecznie musi być najlepszym sposobem na osiągnięcie w pełni elastycznych projektów aplikacji. Tworząc projekt aplikacji, należy go tak skonstruować, aby możliwe było rozszerzanie zachowań poszczególnych elementów bez konieczności modyfikowania istniejącego kodu. Wykorzystując kompozycję oraz delegację, można dodawać nowe zachowania podczas działania programu. Dekorator posługuje się zbiorem klas dekorujących (dekoratorów), które są wykorzystywane do dekorowania poszczególnych obiektów (składników).

Struktura

_images/Decorator.png

Uczestnicy

Component – definiuje interfejs obiektów, do których można dynamicznie dołączyć zobowiązania

ConcreteComponent – konkretna klasa definiująca komponent, z którego korzysta klient

Decorator – zarządza odwołaniem do obiektu klasy Component i definiuje interfejs dopasowany do interfejsu Component

ConcreteDecoratorA, ConcreteDecoratorB - dodaje zobowiązanie do komponentu

Client – używa zarówno obiektów klasy Component jak i ich dekorowanych wersji

Konsekwencje

  1. Większa elastyczność niż przy stosowaniu statycznego dziedziczenia. Wykorzystując dekoratory można dodawać i usuwać zobowiązania w czasie wykonywania programu. Uwzględnienie różnych klas Decorator dla określonej klasy komponentu umożliwia mieszanie i dopasowywanie zobowiązań. Dekoratory ułatwiają także dwukrotne dołączanie właściwości (np. fotografia z podwójną ramką).

  2. Unikanie przeładowania właściwościami klas na szczycie hierarchii. Możliwe jest zdefiniowanie prostej klasy i przyrostowe rozszerzanie jej funkcjonalności za pomocą obiektów dekoratora. Nowe rodzaje dekoratorów są łatwe do zdefiniowania.

  3. Dekorator i jego komponent nie są identyczne. Dekorator działa jak przezroczysta otoczka, jednak z punktu widzenia identyczności obiektów udekorowany komponent nie jest taki sam jak ten wyjściowy.

  4. Wiele małych obiektów. Projekty wykorzystujące dekoratory prowadzą często do powstawanie systemów z dużą liczbą małych, podobnych do siebie obiektów.

Implementacja

  1. Zgodność interfejsów. Interfejs obiektu będącego dekoratorem musi odpowiadać interfejsowi dekorowanego przez niego komponentu. Klasy ConcreteDecorator muszą dziedziczyć po wspólnej klasie.

  2. Pomijanie klasy abstrakcyjnej Decorator. Gdy zależy nam na dodaniu tylko jednego zobowiązania, nie musimy definiować klasy abstrakcyjnej Decorator.

  3. Utrzymanie klas Component w wadze lekkiej poprzez zdefiniowanie klasy abstrakcyjnej Component.

Wzorce pokrewne

  1. Adapter – dekorator zmienia zobowiązania obiektu, a nie jego interfejs. Adapter dodaje obiektowi zupełnie nowy interfejs.

  2. Composite – dekoratora można uważać za zdegenerowany kompozyt, z jednym komponentem. Dekorator jednak dodaje dodatkowe zobowiązania, nie jest przeznaczony do agregacji obiektów.

  3. Strategy – dekorator umożliwia zmianę skóry obiektu, a Strategy zmianę jego wnętrza.

Podsumowanie

  1. Umożliwia dynamiczne dodanie zobowiązanie do obiektu.

  2. Posługuje się zbiorem klas dekorujących (dekoratorów), które są wykorzystywane do dekorowania poszczególnych obiektów (składników).

  3. Klasy dekorujące odzwierciedlają typy obiektów dekorowanych.

  4. Dekoratory są tego samego typu, co obiekty dekorowane, niezależnie, czy zostało to osiągnięte metodą dziedziczenia czy implementacji odpowiednich interfejsów.

  5. Dekoratory zmieniają zachowania obiektów dekorowanych (składników), dodając nowe zachowania przed wywołaniami metod danego składnika i (lub) po nich lub nawet pomiędzy nimi.

  6. Każdy składnik może być „otoczony” dowolną ilością dekoratorów.