Blog
Dekorator
Powrót do Artykułów Dodał(a) Bartłomiej Zagórski w dniu 09.05.2021 w kategorii Wzorce projektowe
Wzorzec Dekorator jest to strukturalny wzorzec projektowy, który pozwala dynamicznie dodać pewnemu obiektowi nowe zachowanie, nie wpływając na inne obiekty danej klasy.
Kiedy stosować wzorzec Dekorator?
Wzorzec ten stosujemy kiedy chcemy rozszerzyć funkcjonalność danego obiektu (udekorować go). Pierwsze co przychodzi na myśl to stworzenie struktury klas, z jedną klasą nadrzędną, a następnie z klasami dziedziczącymi. Jednak w takim wypadku, jeżeli będziemy mieć wiele możliwości rozszerzenia obiektu i niekoniecznie wszystkie muszą zajść jednocześnie, będziemy musięli stworzyć mnóstwo kombinacji klas dziedziczących. Dlatego zastępujemy mechanizm dziedziczenia poprzez kompozycję i delegację.
Żeby to osiągnąć postępujemy następująco. Tworzymy główną klasę abstrakcyjną, z której będziemy tworzyli Klasy stanowiące trzon finalnego produktu. Tworzymy również klasę abstrakcyjną Dekorator, która rozszerza klasę główną i posiada obiekt będący rozszerzeniem klasy głównej. Dzięki temu możemy dekorować zarówno obiekty proste jak i już udekorowane. Następnie Dekorator nadpisuje metody klasy głównej, a wewnątrz nich może wykorzystać metody obiektu, który jest dekorowany. Zobaczmy to na przykładzie.
public abstract class Samochod {
String opis = "nieznane auto";
public String getOpis() {
return opis;
}
abstract double koszt ();
}
Powyżej widzimy naszą główną klasę abstrakcyjną. Ma ona dwie metody, z czego jedna ma domyślną implementację a druga jest typową metodą abstrakcyjną. Następnie przy pomocy dziedziczenia stworzymy dwie klasy stanowiące trzon finalnego produktu.
public class Volkswagen extends Samochod{
public Volkswagen() {
opis = "Volkswagen";
}
@Override
double koszt() {
return 10000;
}
}
public class Renault extends Samochod{
public Renault() {
opis = "Renault";
}
@Override
double koszt() {
return 8000;
}
}
Stworzyliśmy dwie podklasy, w konstruktorach ustawiliśmy właściwe opisy oraz zaimplementowaliśmy metodę koszt. Następnie przejdziemy do stworzenia naszego Dekoratora.
public abstract class SamochodDekorator extends Samochod{
@Override
public abstract String getOpis();
}
Na początek tworzymy klasę abstrakcyjną. Dzięki takiemu mechanizmowi każdy SamochodDekorator jest Samochodem, zatem można polimorficznie przypisać do Samochodu każdy obiekt udekorowany, a także sam Dekorator będzie mógł dekorować obiekty już udekorowane. Ponadto deklarujemy metodę getOpis jako abstrakcyjną, żeby każdy dekorator musiał ją na nowo zaimplementować. Ma to o tyle znaczenie, że za chwilę będziemy modyfikować opisy. Zobaczmy teraz przykładowe dekoratory.
public class FourMotionDekorator extends SamochodDekorator{
Samochod samochod;
public FourMotionDekorator(Samochod samochod) {
this.samochod = samochod;
}
@Override
double koszt() {
return samochod.koszt() + 1000;
}
@Override
public String getOpis() {
return samochod.getOpis() + " + napęd na cztery koła";
}
}
public class NawigacjaDekorator extends SamochodDekorator{
Samochod samochod;
public NawigacjaDekorator(Samochod samochod) {
this.samochod = samochod;
}
@Override
double koszt() {
return samochod.koszt() + 500;
}
@Override
public String getOpis() {
return samochod.getOpis() + " + nawigacja";
}
}
Zarówno dekorator dodający napęd na cztery koła jak i nawigację dostają odwołanie do obiektu Samochód. Następnie wykorzystują metody tego samochodu jako swego rodzaju bazę i rozszerzają je. Mogłyby bez problemu również dodać nowe metody, właściwe tylko dla siebie. Zobaczmy teraz kod Klienta.
public class DekoratorTest {
public static void main(String[] args) {
Samochod volkswagen = new Volkswagen();
Samochod renault = new Renault();
Samochod auto = new FourMotionDekorator(volkswagen);
System.out.println(auto.getOpis() + " kosztuje: " + auto.koszt());
auto = new NawigacjaDekorator(auto);
System.out.println(auto.getOpis() + " kosztuje: " + auto.koszt());
auto = new FourMotionDekorator(renault);
System.out.println(auto.getOpis() + " kosztuje: " + auto.koszt());
auto = new NawigacjaDekorator(auto);
System.out.println(auto.getOpis() + " kosztuje: " + auto.koszt());
}
}
Tworzymy tutaj 2 trzony volkswagena i renault, a następnie dekorujemy każdy z nich najpierw dodając do nich napęd na cztery koła, a następnie już tak udekorowanemu obiektowi dokładamy jeszcze nawigację, wynik działania programu jest następujący

Jak widać dekorator działa jak należy. Możemy dodawać nowe funkcjonalności zarówno do obiektów klas bezpośrednio rozszerzających główną klasę abstrakcyjną jak i do dekoratorów. Możemy dodawać różne funkcje bez konieczności tworzenia wszystkich kombinacji klas głównych i dodatków.
Podsumowanie
Dzięki wzorcowi dekorator możemy rozszerzać funkcjonalności klas, niekoniecznie korzystając wyłącznie z dziedziczenia. Obiekty, które korzystają z obiektów udekorowanych nie mają świadomości o tym, ze obiekt główny został opakowany dekoratorami ze względu na to, że dekoratory są tego samego typu co główne składniki (rozszerzają tą samą klasę abstrakcyjną lub implementują ten sam interfejs). Dzięki dekoratorom możemy dodawać nowe zachowania do obiektu w trakcie trwania programu. Ponadto wzorzec ten spełnia regułę programowania obiektowego otwarte/zamknięte. Nie musimy wprowadzać żadnych zmian w kodzie, żeby rozszerzyć funkcjonalność jakiejś klasy.