Wzorce projektowe – Dekorator

 lis, 28 - 2017   PHPWzorce Projektowe

Wzorce projektowe - DekoratorDekorator jest ciekawym wzorcem projektowym, który w pewnych okolicznościach może być niezwykle użyteczny. Należy do grupy wzorców strukturalnych i pozwalającym na dynamiczne rozszerzenie funkcjonalności danej klasy. Odbywa się to poprzez opakowanie klasy bazowej w inne klasy rozszerzające jej działanie.

 

 

 

Charakterystyka wzorca dekorator

 

Dekorator pozwala na rozbudowę istniejącej klasy, dodając lub zmieniając jej zachowania, bez potrzeby dziedziczenia. Wykorzystanie tego wzorca projektowego polega na opakowaniu klasy bazowej (dekorowanej) w klasę dekorującą.

Zaletą wykorzystania tego wzorca jest bardzo duża elastyczność poprzez rozbicie programu na wiele mniejszych klas, które mogą dynamicznie zmieniać działanie klasy bazowej. Aplikacja napisana w ten sposób pozwala na łatwe rozszerzanie o nowe funkcjonalności. Wadą jest rozbicie projektu na małe klasy, które często są do siebie bardzo podobne.

 

Wzorzec dekorator nie jest bardzo skomplikowany w zrozumieniu i użyciu. Poniższy diagram UML przedstawia jego podstawową strukturę. Podstawą jest wspólny interfejs, który jest implementowany zarówno przez klasę dekorowaną jak i wszystkie klasy dekorujące.

 

Dekorator - diagram UML

 

 

Przykład zastosowania wzorca dekorator – tworzenie kanapek

 

Najlepiej pokazać działanie wzorca na przykładzie. Stwórzmy prosty skrypt, którego zadaniem będzie robienie kanapek z wykorzystaniem różnych składników. W zależności od dobranych składników, cena za kanapkę będzie się różniła.

Zacznijmy od napisania kontraktu (interfejsu) naszej kanapki. W celach demonstracyjnych wystarczą nam dwie metody, które zwracają cenę i listę składników.

Ok., teraz napiszmy naszą klasę bazową dla kanapki. Każda kanapka musi składać się z podstawowych składników, jakimi są: bułka, masło i majonez. Musimy również zainicjować cenę.

Mając podstawowy model kanapki, możemy przystąpić do jej rozbudowy (dekorowania) o dodatkowe składniki. Każdy składnik piszemy, jako osobną klasę, w naszym przykładzie będą to salami, ser i warzywa.

Każda z klas dekorowanych implementuje interfejs kanapki. Ponadto w konstruktorze ustawia instancję kanapki, która następnie jest rozwijana przy pomocy implementowanych metod.

 

 

Skrypt jest gotowy do użycia, jednak porównując klasy dekorujące można zauważyć, że są one niemal identyczne. Łamie to świętą zasadę DRY – Don’t Repeat Yourself – Nie Powtarszaj Się. Powyższy kod można zrefaktoryzować upraszczając nieco jego strukturę. Zacznijmy od napisania abstrakcyjnej klasy, reprezentującej bliżej nieokreślony składnik naszej kanapki.

Następnie wystarczy jedynie rozszerzyć abstrakcyjny dodatek do kanapki o jego konkretne implementacje, w których wystarczy jedynie określić właściwości dla poszczególnych składników.

 

Jak widzimy, nasz kod mocno się skrócił i uprościł. Przyszedł czas na przetestowanie przykładu i stworzenie konkretnej kanapki.

Najpierw tworzymy instancje klasy bazowej, czyli w powyższym przykładzie jest to klasa podstawowej kanapki. Następnie obudowujemy ją o dodatkowe składniki, przekazując aktualną wersje kanapki do kolejnej klasy dekorującej. Jako rezultat otrzymujemy w pełni skomponowaną kanapkę.

Zmiana w klasy dekorującej spowoduje automatyczną aktualizację końcowej ceny oraz listy składników.

 

Tak napisany skrypt można rozbudowywać dalej o nowe składniki, które działają jako niezależne obiekty, mogące być dodawane lub odejmowane dynamicznie podczas działania aplikacji. Program zachowuje zasadę OCP (Open-Close Principle), czyli jest otwarty na rozszerzenia, ale zamknięty na modyfikację.

 

Powyższy przykład można pobrać tutaj: decorator-sandwich

Przykład na GitHub: https://github.com/molitorys/design-patterns/tree/master/src/Decorator/Sandwich

 

 

Podsumowanie

 

Wzorzec projektowy dekorator możemy użyć tam, gdy:

  • istnieje konieczność dodawania nowych funkcjonalności do poszczególnych obiektów w sposób dynamiczny, bez wpływu na inne obiekty
  • potrzebny jest mechanizm cofania wprowadzanych do obiektów zmian
  • rozszerzanie funkcjonalnosci przez tworzenie kolejnych podklas jest niepraktyczne i powoduje zbyt duże zamieszanie w kodzie

Wykorzystanie omawianego wzorca daje większą elastyczność niż zwykłe dziedziczenie oraz pozwala na uniknięcie tworzenia przeładowancyh funkcjami klas. Jak zawsze są jakieś minusy, np. konieczność tworzenia wielu małych klas, ale zdecydowanie zalety przesłaniają wady. Zachęcam do zapoznania się z wzorcem dekorator.


Przeczytaj równierz