Kihagyás

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 final osztá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.");
    }
}