Decorator (Dektorator) to strukturalny wzorzec projektowy, jest to jeden z najpraktyczniejszych wzorców projektowych. Dlatego sprawdza się on bardzo dobrze w przypadkach gdzie mamy zaawansowaną hierarchię klas modelowych i zależności między nimi.
Decorator – przykład zastosowania
Załóż, że musisz stworzyć aplikacje dla dealera samochodów. W ofercie jest 50 różnych modeli. Każdy z tych modelów ma po 3 wersje wyposażenia.
W rezultacie musisz utworzyć 50 klas modelowych po 3 warianty każda, co daje Ci finalnie 150 klas. Możesz próbować zredukować ich ilość poprzez wykorzystanie dziedziczenia, ale najpewniej zakończy się to bardzo złożonym i nadal obszernym kodem.
Jednak to nie koniec problemów. Dla każdego pokazu musisz uwzględnić dodatki, które klient może sobie zażyczyć – np. podłokietnik, koło zapasowe, dodatkowy zestaw opon. Pierwsze co nasuwa się na myśl, to seria if’ów, którymi będziemy dokonywać sprawdzenia jakie klient dodatki do auta wybrał – jednak czy wyobrażasz sobie tą ogromną ilość instrukcji warunkowych?
Wyzwanie jest niebagatelne, jednak jest to na tyle znana sytuacja problemowa, że ktoś już wcześniej wpadł na jej rozwiązanie i nazwał „Decorator”, który przeszedł do kanonu jako wzorzec projektowy.
Decorator – diagram klas
W poszczególne miejsca powstawiajmy teraz konkretne wartości:
- Component = najbardziej ogólna klasa pojazdu np. Car
- CreateComponent = przykład modelu dla pojazdu np. Hyundai i20, Hyundai i30, itd..
- Decorator = klasa dekorująca, czyli wzbogacająca obiekt typu CreateComponent. W naszym przykładzie będzie to CarAccessories (dodatki)
- ConcreteDecorator – konkretny dodatek do pojazdu np. AirConditioning lub Armrest (podłokietnik)
Dlatego podstawiamy pod diagram konkretne wartości i otrzymujemy:
Decorator – przykład implementacji
Teraz dla drugiego diagramu tworzymy poszczególne klasy.
Car
W pierwszej kolejności prosty przykład klasy Car. Powinna to być klasa abstrakcyjna, ponieważ instancje muszą być tworzone na podstawie klas po niej dziedziczących.
public abstract class Car { public abstract float price(); public abstract String description(); }
i20Car
Następnie tworzymy wszystkie modele dla pojazdów, które dziedziczyć będą po klasie Car. W moim przypadku będzie to tylko i20Car, ale mogą być to kolejne i30Car, itd…
public class i20Car extends Car { @Override public float price() { return 20000; } @Override public String description() { return "Hyundai i20"; } }
Decorator
Właściwy dekorator, czyli wszystko to co będzie dekorowało poszczególne pojazdy – jest to klasa abstrakcyjna, która dziedziczy po pierwotnej klasie abstrakcyjnej Car, nie potrzebuje ona implementacji.
public abstract class CarAcessoriesDecorator extends Car {}
ConcreteDecorator – Armrest
Podobnie zaimplementujemy najważniejszą klasę dla dodatku. Jednak może być ich dowolna ilość, w moim przypadku będzie to jedna – Armreset.
Przede wszystkim klasa ta musi zawierać konstruktor który który przyjmuje dekorowany obiekt – w tym przypadku instancja typu Car.
public class Armrest extends CarAcessoriesDecorator { private Car car; public Armrest(Car car) { this.car = car; } @Override public float price() { return car.price() + 500F; } @Override public String description() { return car.description() + " with armrest"; } }
Przykład działania wzorca projektowego Decorator
Finalnie tworzymy instancje pojazdu, a następnie możemy ją udekorować:
public static void main(String[] args) { Car i20Car = new i20Car(); System.out.println("i20 price: " + i20Car.price()); System.out.println("i20 description: " + i20Car.description()); Car i20withArmrester = new Armrest(i20Car); System.out.println("i20withArmrester price: " + i20withArmrester.price()); System.out.println("i20withArmrester description: " + i20withArmrester.description()); }
Rezultat działania:
i20 price: 20000.0 i20 description: Hyundai i20 i20withArmrester price: 20500.0 i20withArmrester description: Hyundai i20 with armrest
Wzorzec Decorator – wnioski
Podsumowując – uznałem ten wzorzec jako jeden z najbardziej podstawowych, gdyż nie ma innej alternatywy do rozwiązywania podobnie skomplikowanych przypadków. Dlatego zaprezentowałem go w ramach mojego Szkolenia Wzorce Projektowe. Szczególnie dobrze jest go wdrożyć, kiedy mamy wiele klas modelowych i ciężko zapanować nam nad projektem. Warto również zapoznać z tym wzorcem projektowym swojego kolegę, który nagminnie wykorzystuje dziedziczenie 🙂