
Dekorator
Wzorzec dekorator (ang. decorator pattern) należy do grupy strukturalnych wzorców projektowych. Pozwala na rozszerzanie lub zmienianie funkcjonalności obiektów podczas wykonywania poprzez opakowanie ich jedną lub większą ilością klas dekoratorów.
Wyobraźmy sobie, że prowadzimy lokal z domowymi obiadami. Nasz lokal w swojej ofercie ma dwa podstawowe dania:
Danie 1: ryż z sosem i kurczakiem
Danie 2: ziemniaki z sosem, surówką i kotletem schabowym
Przykładowy kod bez dekoratora:
Lista klas:

Klasa Danie:
public class Danie {
private String opis;
private int cena;
public Danie(String opis, int cena) {
this.opis = opis;
this.cena = cena;
}
public String getOpis() {
return opis;
}
public int getCena() {
return cena;
}
}
Klasa DanieRyz:
public class DanieRyz extends Danie {
public DanieRyz() {
super("Ryż z kurczakiem polany sosem", 26);
}
}
Klasa DanieZiemniaki:
public class DanieZiemniaki extends Danie {
public DanieZiemniaki() {
super("Ziemniaki z kotletem schabowym polane sosem", 26);
}
}
Klasa Main:
public class Main {
public static void main(String[] args) {
Danie danieZZiemniakami = new DanieZiemniaki();
Danie danieZRyzem = new DanieRyz();
System.out.println("Przygotowano: " + danieZZiemniakami.getOpis() + ", cena: " + danieZZiemniakami.getCena() + " zł");
System.out.println("Przygotowano: " + danieZRyzem.getOpis() + ", cena: " + danieZRyzem.getCena() + " zł");
}
}
Można by się zastanowić po co używać wzorca projektowego dekorator, przecież klient otrzymał danie, które zamówił. Jednakże co w przypadku, gdybyśmy chcieli dodać nowe dania do menu np. ziemniaki z kurczakiem i buraczkami albo klient chciałby sam sobie skomponować danie? Robi się problem, ponieważ za każdym razem musimy tworzyć nowe klasy jako nowe dania. Dlatego, aby uniknąć ciągłego namnażania się klas możemy zastosować wzorzec projektowy dekorator.
Wygląd kodu po implementacji wzorca:
Lista klas:

Klasa Danie:
public abstract class Danie {
private String opis;
private int cena;
public Danie(String opis, int cena) {
this.opis = opis;
this.cena = cena;
}
public String getOpis() {
return opis;
}
public int getCena() {
return cena;
}
}
Klasa DanieDekorator:
public abstract class DanieDekorator extends Danie {
protected Danie danie;
public DanieDekorator(Danie danie) {
super("", 0);
this.danie = danie;
}
}
Klasa Ziemniaki:
public class Ziemniaki extends Danie {
public Ziemniaki() {
super("ziemniaki", 10);
}
}
Klasa Ryz:
public class Ryz extends Danie {
public Ryz() {
super("ryż", 10);
}
}
Klasa KurczakDekorator:
public class KurczakDekorator extends DanieDekorator {
public KurczakDekorator(Danie danie) {
super(danie);
}
@Override
public String getOpis() {
return danie.getOpis() + ", kurczak";
}
@Override
public int getCena() {
return danie.getCena() + 12;
}
}
Klasa SchabowyDekorator:
public class SchabowyDekorator extends DanieDekorator {
public SchabowyDekorator(Danie danie) {
super(danie);
}
@Override
public String getOpis() {
return danie.getOpis() + ", kotlet schabowy";
}
@Override
public int getCena() {
return danie.getCena() + 12;
}
}
Klasa BuraczkiDekorator:
public class BuraczkiDekorator extends DanieDekorator {
public BuraczkiDekorator(Danie danie) {
super(danie);
}
@Override
public String getOpis() {
return danie.getOpis() + ", buraczki";
}
@Override
public int getCena() {
return danie.getCena() + 6;
}
}
Klasa SosDekorator:
public class SosDekorator extends DanieDekorator {
public SosDekorator(Danie danie) {
super(danie);
}
@Override
public String getOpis() {
return danie.getOpis() + ", sos";
}
@Override
public int getCena() {
return danie.getCena() + 4;
}
}
Klasa Main:
public class Main {
public static void main(String[] args) {
Danie danieZZiemniakami = new BuraczkiDekorator(new KurczakDekorator(new Ziemniaki()));
System.out.println("Danie składa się z: " + danieZZiemniakami.getOpis() + ". Cena: " + danieZZiemniakami.getCena() + " zł");
Danie danieZRyzem = new SosDekorator(new BuraczkiDekorator(new SchabowyDekorator(new Ryz())));
System.out.println("Danie składa się z: " + danieZRyzem.getOpis() + ". Cena: " + danieZRyzem.getCena() + " zł");
}
}
Wynik wywołania w klasie Main:

Na pierwszy rzut oka mogłoby się wydawać, że teraz nasz kod jest bardziej rozbudowany i skomplikowany. Mieliśmy ograniczyć tworzenie nowych klas, a my już na samym początku mamy ich znacznie więcej. Jednakże, dzięki rozbiciu naszych podstawowych dań na poszczególne składniki możemy dowolnie komponować dania i bez trudu dodaliśmy dodatkowy składnik do wyboru “buraczki”. W ten sposób uniknęliśmy tworzenia klas wszystkich możliwych wariantów naszych dań.