Decorator (Díszítő)
Kategória: Strukturális minta (Structural Pattern)
Lényege és célja
A Decorator minta lehetővé teszi, hogy futásidőben, dinamikusan adjunk hozzá új viselkedést vagy állapotot egy meglévő objektumhoz, anélkül, hogy az eredeti osztály forráskódját módosítanánk.
Ezt úgy éri el, hogy a meglévő objektumot "becsomagolja" egy speciális burkoló (wrapper) osztályba, amely ugyanazt az interfészt implementálja, mint a becsomagolt objektum.
Miért jobb ez, mint a sima öröklés?
Képzeljük el a kávézós példát örökléssel: létre kéne hoznunk a SimpleCoffee, CoffeeWithMilk, CoffeeWithSugar, CoffeeWithMilkAndSugar stb. osztályokat. Ha bejön a karamellszirup, az alosztályok száma exponenciálisan megnő. Ezt hívják osztályrobbanásnak (class explosion).
A Decorator minta ezzel szemben a kompozíciót használja: 1. IS-A (Öröklés/Implementáció): A dekorátor egyfajta kávé, így a kliens (aki issza) nem is tudja, hogy egy díszített objektummal van dolga. 2. HAS-A (Kompozíció): A dekorátor tartalmaz egy másik kávét (amit konstruktorban kap meg), és a kéréseket (árazás, leírás) delegálja neki, majd a kapott eredményhez hozzáadja a saját extráját (pl. +100 Ft, ", tej"). 3. Ennek a kettőnek a kombinációja miatt a dekorátorok szabadon láncolhatók egymásba (matrjoska baba elv).
Mikor használjuk?
- Amikor extra funkciókat vagy felelősségeket akarunk ráaggatni egyedi objektumokra futásidőben, a többi példány befolyásolása nélkül.
- Amikor az osztályok örökléssel való kiterjesztése nehézkes lenne a túl sok lehetséges kombináció miatt.
- Amikor egy
finalosztályt szeretnénk új funkciókkal felruházni (bár ehhez kell, hogy legyen egy közös interfész, amit implementálhatunk).
Mermaid Diagram
Az ábrán jól látható, hogy az absztrakt CoffeeDecorator egyszerre implementálja a Coffee interfészt, és tart fenn egy kompozíciós kapcsolatot egy másik Coffee példánnyal. A konkrét dekorátorok már csak a saját extráikkal egészítik ki a működést.
classDiagram
class Coffee {
<<interface>>
+getCost() int
+getDescription() String
}
class SimpleCoffee {
+getCost() int
+getDescription() String
}
class CoffeeDecorator {
<<abstract>>
#coffee: Coffee
+CoffeeDecorator(c: Coffee)
+getCost() int
+getDescription() String
}
class MilkDecorator {
+MilkDecorator(c: Coffee)
+getCost() int
+getDescription() String
}
class SugarDecorator {
+SugarDecorator(c: Coffee)
+getCost() int
+getDescription() String
}
SimpleCoffee ..|> Coffee : "implementálja"
CoffeeDecorator ..|> Coffee : "implementálja"
%% Ez a nyíl jelöli a kompozíciót (a Decorator becsomagol egy Coffee-t)
CoffeeDecorator o--> Coffee : "tartalmazza (kompozíció)"
MilkDecorator --|> CoffeeDecorator : "örökli"
SugarDecorator --|> CoffeeDecorator : "örökli" Forráskód
package structural.decorator;
interface Coffee {
int getCost();
String getDescription();
}
class SimpleCoffee implements Coffee {
@Override
public int getCost() {
return 500;
}
@Override
public String getDescription() {
return "Espresso";
}
}
abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
@Override
public int getCost() {
return coffee.getCost();
}
@Override
public String getDescription() {
return coffee.getDescription();
}
}
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public int getCost() {
return coffee.getCost() + 100;
}
@Override
public String getDescription() {
return coffee.getDescription() + ", tej";
}
}
class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
@Override
public int getCost() {
return coffee.getCost() + 50;
}
@Override
public String getDescription() {
return coffee.getDescription() + ", cukor";
}
}
class Main {
static void main() {
var simpleCoffee = new SimpleCoffee();
var milkCoffee = new MilkDecorator(simpleCoffee);
var sugarCoffee = new SugarDecorator(milkCoffee);
var extraSugarCoffee = new SugarDecorator(sugarCoffee);
System.out.println(extraSugarCoffee.getDescription() + " - " + extraSugarCoffee.getCost() + " Ft.");
}
}