Jedną z najważniejszych zasad w tworzeniu oprogramowania jest zasada projektowania otwarte-zamknięte. Ta zasada projektowania podkreśla, że klasy powinny być otwarte na rozbudowę, ale zamknięte na modyfikację. Wzorzec projektowy dekoratora ucieleśnia zasadę projektowania otwarte-zamknięte.
Dzięki wzorcowi projektowemu dekoratora możesz łatwo rozszerzyć klasę, nadając jej nowe zachowanie bez zmiany istniejącego kodu. Wzorzec dekoratora robi to dynamicznie w czasie wykonywania, używając kompozycji. Ten wzorzec projektowy jest znany jako elastyczna alternatywa dla używania dziedziczenia w celu rozszerzenia zachowania.
Jak działa wzorzec projektowy dekoratora?
Chociaż wzór dekoratora jest alternatywą dla dziedziczenie klasy, uwzględnia w swoim projekcie pewne aspekty dziedziczenia. Kluczowym aspektem wzorca dekoratora jest to, że wszystkie jego klasy są ze sobą powiązane, bezpośrednio lub pośrednio.
Typowy wzorzec projektowy dekoratora ma następującą strukturę:
Z powyższego diagramu klas widać, że wzorzec dekoratora ma cztery główne klasy.
Część: jest to klasa abstrakcyjna (lub interfejs), która służy jako nadtyp wzorca dekoratora.
Komponent betonowy: są to obiekty, które można ozdobić różnymi zachowaniami w czasie wykonywania. Dziedziczą z interfejsu komponentu i implementują jego abstrakcyjne funkcje.
Dekorator: ta klasa jest abstrakcyjna i ma ten sam typ nadrzędny, co obiekt, który będzie dekorować. Na diagramie klas zobaczysz dwie relacje między klasami komponentów i dekoratorów. Pierwszy związek to związek dziedziczenia; każdy dekorator jest część. Drugi związek dotyczy kompozycji; każdy dekorator ma (lub zawija) komponent.
BetonDekorator: są to indywidualne dekoratory, które nadają komponentowi określone zachowanie. Należy zauważyć, że każdy konkretny dekorator ma zmienną instancji, która zawiera odniesienie do komponentu.
Implementacja wzorca projektowego Decorator w Javie
Przykładowa aplikacja do zamawiania pizzy może odpowiednio zademonstrować, jak używać wzorca dekoratora do tworzenia aplikacji. Ta przykładowa aplikacja do pizzy umożliwia klientom zamawianie pizzy z wieloma dodatkami. Pierwszą klasą wzorca dekoratora jest interfejs pizzy:
publicznyinterfejsPizza{
publicznyabstrakcyjny Strunowy opis();
publicznyabstrakcyjnypodwójniekoszt();
}
Interfejs Pizza jest klasą komponentów. Możesz więc utworzyć z niego jedną lub więcej konkretnych klas. Firma zajmująca się pizzą produkuje dwa główne rodzaje pizzy na bazie ich ciasta. Jeden rodzaj pizzy ma ciasto drożdżowe:
publicznyklasaCiasto DrożdżowePizzaprzyboryPizza{
@Nadpisanie
publiczny Strunowy opis(){
powrót„Ciasto do pizzy na drożdżach”;
}
@Nadpisanie
publicznypodwójniekoszt(){
powrót18.00;
}
}
YeastCrustPizza to pierwszy beton klasa Javy interfejsu Pizza. Drugim dostępnym rodzajem pizzy jest płaski chleb:
publicznyklasaPłaski Chleb CiastoPizzaprzyboryPizza{
@Nadpisanie
publiczny Strunowy opis(){
powrót„Ciasto na pizzę z chleba płaskiego”;
}
@Nadpisanie
publicznypodwójniekoszt(){
powrót15.00;
}
}
Klasa FlatbreadCrustPizza jest drugim konkretnym komponentem i podobnie jak klasa YeastCrustPizza implementuje wszystkie abstrakcyjne funkcje interfejsu Pizza.
Dekoratorzy
Klasa dekoratora jest zawsze abstrakcyjna, więc nie można bezpośrednio z niej utworzyć nowej instancji. Ale konieczne jest ustanowienie relacji między różnymi dekoratorami a elementami, które będą dekorować.
publicznyabstrakcyjnyklasaToppingDecoratorprzyboryPizza{
publiczny Strunowy opis(){
powrót„Nieznany dodatek”;
}
}
Klasa ToppingDecorator reprezentuje klasę dekoratora w tej przykładowej aplikacji. Teraz pizzeria może tworzyć wiele różnych dodatków (lub dekoratorów), korzystając z klasy ToppingDecorator. Załóżmy, że pizza może mieć trzy różne rodzaje dodatków, a mianowicie ser, pepperoni i grzyby.
Nadzienie Serowe
publicznyklasaSerrozciąga sięToppingDecorator{
prywatny pizza pizza;publicznySer(pizza-pizza){
Ten.pizza = pizza;
}@Nadpisanie
publiczny Strunowy opis(){
powrót pizza.opis() + ", Polewa serowa";
}
@Nadpisanie
publicznypodwójniekoszt(){
powrótPizza.koszt() + 2.50;
}
}
Nadzienie Pepperoni
publicznyklasaPepperonirozciąga sięToppingDecorator{
prywatny pizza pizza;publicznyPepperoni(pizza-pizza){
Ten.pizza = pizza;
}@Nadpisanie
publiczny Strunowy opis(){
powrót pizza.opis() + ", Polewa Pepperoni";
}
@Nadpisanie
publicznypodwójniekoszt(){
powrótPizza.koszt() + 3.50;
}
}
Nadzienie Pieczarkowe
publicznyklasaGrzybrozciąga sięToppingDecorator{
prywatny pizza pizza;publicznyGrzyb(pizza-pizza){
Ten.pizza = pizza;
}
@Nadpisanie
publiczny Strunowy opis(){
powrót pizza.opis() + ", Polewa Grzybowa";
}
@Nadpisanie
publicznypodwójniekoszt(){
powrótPizza.koszt() + 4.50;
}
}
Teraz masz prostą aplikację zaimplementowaną przy użyciu wzorca projektowego dekoratora. Jeśli klient zamówiłby pizzę na cieście drożdżowym z serem i pepperoni, kod testowy dla tego scenariusza będzie wyglądał następująco:
publicznyklasaGłówny{
publicznystatycznypróżniagłówny(String[] argumenty){
pizza pizza1 = nowy Ciasto DrożdżowePizza();
pizza1 = nowy Pepperoni (pizza 1);
pizza1 = nowy Ser (pizza1);
System.out.println (pizza1.description() + " $" + pizza1.koszt());
}
}
Uruchomienie tego kodu spowoduje wyświetlenie w konsoli następującego wyniku:
Jak widać, dane wyjściowe określają rodzaj pizzy wraz z jej całkowitym kosztem. Pizza zaczęła się jako pizza na cieście drożdżowym za 18,00 $, ale dzięki wzorowi dekoratora aplikacja była w stanie dodać nowe funkcje i ich odpowiedni koszt do pizzy. W ten sposób nadanie pizzy nowego zachowania bez zmiany istniejącego kodu (pizza ze skórką drożdżową).
Dzięki wzorowi dekoratora możesz zastosować to samo zachowanie do obiektu tyle razy, ile chcesz. Jeśli klient zamawia pizzę ze wszystkim, co na niej jest i trochę dodatkowego sera, możesz zaktualizować główną klasę za pomocą następującego kodu, aby to odzwierciedlić:
pizza pizza2 = nowy Ciasto DrożdżowePizza();
pizza2 = nowy Pepperoni (pizza 2);
pizza2 = nowy Ser (pizza2);
pizza2 = nowy Ser (pizza2);
pizza2 = nowy Grzyb (pizza2);System.out.println (pizza2.description() + " $" + pizza2.koszt());
Zaktualizowana aplikacja wygeneruje następujące dane wyjściowe w konsoli:
Zalety korzystania ze wzorca projektowego Decorator
Dwie główne zalety korzystania ze wzorca projektowego dekoratora to bezpieczeństwo i elastyczność. Wzorzec dekoratora umożliwia opracowanie bezpieczniejszego kodu bez ingerencji w istniejący wcześniej bezpieczny kod. Zamiast tego rozszerza istniejący kod poprzez kompozycję. Skuteczne zapobieganie wprowadzaniu nowych błędów lub niezamierzonych skutków ubocznych.
Ze względu na kompozycję wywoływacz ma również dużą elastyczność podczas korzystania ze wzoru dekoratora. W dowolnym momencie możesz zaimplementować nowy dekorator, aby dodać nowe zachowanie, bez zmiany istniejącego kodu i zakłócania działania aplikacji.