Díszítő programtervezési minta

A Wikipédiából, a szabad enciklopédiából

Az objektumorientált programozásban (OOP) a díszítő programtervező minta egy olyan programtervezési minta, amely lehetővé teszi adott objektumokhoz más viselkedések hozzáadását akár statikusan, akár dinamikusan, anélkül, hogy hatással lenne az azonos osztályból származó többi objektumra. [1]

Bevezetés[szerkesztés | forrásszöveg szerkesztése]

Díszítő minta UML osztály diagramja

A díszítő minta arra használható, hogy kiterjeszthető legyen (díszítés) számos objektum funkcionalitása statikusan vagy néhány esetben futási időben, függetlenül más azonos osztályhoz tartozó példányoktól, mindehhez alapozást nyújtva tervezési időben. Mindez megvalósítható egy új díszítő osztály tervezésével, amely becsomagolja az eredeti osztályt. A becsomagolás megvalósítható a következő lépések sorozatával:

  1. hozzunk létre egy "díszítő" osztályt az eredeti "komponens" alosztályaként (lásd az UML diagramot);
  2. a díszítő osztályban, adjunk hozzá egy komponens referenciát mezőként;
  3. adjunk át egy komponenst a díszítő osztály konstruktorának, hogy kezdő értékkel töltse fel a komponens referenciát;
  4. a díszítő osztályban minden "Komponens" metódust irányítsuk át a "Komponens" referenciára és
  5. a konkrét díszítő osztályban felüldefiniálhatjuk bármelyik Komponens metódust, amelyik viselkedését módosítani szükséges.

Ezt a mintát arra tervezték, hogy többszörös díszítőket lehessen egymásra halmozni a másik tetején, minden alkalommal új funkcionalitást adva hozzá a felüldefiniált metódus(ok)hoz.

Jegyezzük meg, hogy a díszítők és az eredeti osztály objektum közös funkcionalitás halmazon osztoznak. A fenti diagramban "operation()" metódus elérhető a díszítőkben és a díszített objektumban egyaránt.

A díszítő jellegzetességek lehetnek metódusok, tulajdonságok, vagy egyéb tagok, melyeket általában egy interfész deklarál vagy osztály öröklődés definiál, amelyet a díszítők megosztanak egymás közt és a díszítő objektum között. A fenti példában a "Komponens" osztályt öröklik mind a konkrét komponens, mind a díszítőből (Decorator) származó alosztályok.

A díszítő minta alternatívája az alosztályok képzésének. Az alosztályok képzésével viselkedések adhatók hozzá fordítási időben és a változás kihat az eredeti osztály összes példányára, míg a díszítő minta használatával biztosítható új viselkedés futási időben egyéni objektumok számára.

Ez a különbség akkor válik nagyon fontossá, amikor sokféle független módon lehetséges a funkcionalitás kiterjesztése. Néhány objektumorientált programozási nyelvben az osztályok létrehozása futás időben nem lehetséges, és tipikusan nem lehetséges megjósolni tervezési időben, hogy a kiterjesztések milyen kombinációjára lesz szükség később. Ez azt jelentené, hogy egy új osztálynak el kéne készíteni az összes lehetséges kombinációját. Ezzel szemben a díszítők objektumok, melyek futási időben jönnek létre, kombinálhatók használat előtt. Az I/O folyamok megvalósítása mind a Java-ban, mind a .NET keretrendszerben alkalmazzák a díszítő mintát.

Motiváció[szerkesztés | forrásszöveg szerkesztése]

Windows példa UML Diagramja

Példaként vegyünk egy ablakot egy ablakozó rendszerben. Ahhoz, hogy egy ablak tartalmát görgetni tudjuk, szükség lehet arra, hogy megfelelő vízszintes ill. függőleges görgetősávot adjunk hozzá. Tegyük fel, hogy az ablakokat az Window osztály példányai reprezentálnak, és tegyük fel, hogy ennek az osztálynak nem létezik görgetősáv hozzáadása funkcionalitása. Készíthetünk alosztályt a ScrollingWindow néven, amely ellátja ezt a feladatot, vagy készíthetünk egy ScrollingWindowDecorator, amely hozzáadja ezt a funkcionalitást létező Window objektumokhoz. Ezen a ponton, bármelyik megoldás megfelelő lenne.

Most tegyük fel, hogy szeretnénk olyan képességet is, amely keretet ad hozzá az ablakunkhoz. Megint az eredeti Window osztálynak nincs támogatása ehhez. A ScrollingWindow alosztály problémával kerül szembe, mert valójában egy új típusú ablakot hoz létre. Ha szeretnénk keretet hozzáadni az összes ablakoz, új osztályokat kell létrehoznunk WindowWithBorder (keretes ablak) és ScrollingWindowWithBorder (görgetősávos keretes ablak) néven. Nyilvánvalóan ez a probléma rosszabbra fordul, minden új tulajdonság hozzáadásakor. A díszítő megoldás esetén csak egyszerűen készítünk egy új BorderedWindowDecorator (keretes ablak díszítőt) — futási időben, díszíthetjük a létező ablakot ScrollingWindowDecorator-ral (görgethető ablak díszítővel) vagy BorderedWindowDecorator-ral (keretes ablak díszítővel) vagy mindkettővel, igény szerint.

Megjegyzendő, hogy az előző példában a "SimpleWindow" és "Window" osztályok megvalósítják a "Window" interfészt, amely definiálja a "draw();" metódust és "getDescription();" metódust. Ezek szükségesek ebben a felállásban, hogy díszíteni tudjuk az ablak vezérlőt.

Egy másik jó példa, ahol a díszítő használata kívánatos lehet, pl. ha egy objektum tulajdonságainak ill. metódusainak az elérését korlátozni szükséges néhány szabály halmaz szerint vagy esetleg számos párhuzamosan használt szabály halmaz szerint (pl. különböző felhasználói hitelesítések esetén, stb.). Ebben az esetben az eredeti objektumban az elérések szabályzásának megvalósítása helyett, az változatlanul hagyható és bárminemű korlátozástól mentesen használható. Az eredeti objektum becsomagolható egy elérést szabályzó díszítő objektumba, amely kizárólag szolgálhatja ki az eredeti objektum interfészének egy megengedett részhalmazát.

Példák[szerkesztés | forrásszöveg szerkesztése]

Java[szerkesztés | forrásszöveg szerkesztése]

Első példa (ablak/görgetés esete)[szerkesztés | forrásszöveg szerkesztése]

A következő Java példa illusztrálja a díszítők használatát az ablak/görgetés esetén.

// a Window interfész osztály
public interface Window {
    public void draw(); // ablak rajzolás
    public String getDescription(); // visszaadja az ablak leírását
}
 
// egy egyszerű ablak kiterjesztése görgetősáv nélkül
class SimpleWindow implements Window {
    public void draw() {
        //ablak rajzolása
    }
 
    public String getDescription() {
        return "simple window";
    }
}

A következő osztályok díszítőket tartalmaznak az Window osztályhoz, beleértve a díszítő osztályokat magukat is.

// absztrakt díszítő osztály, amely megvalósítja a Window interfészt
abstract class WindowDecorator implements Window {
    protected Window decoratedWindow; // the Window being decorated
 
    public WindowDecorator (Window decoratedWindow) {
        this.decoratedWindow = decoratedWindow;
    }
    public void draw() {
        decoratedWindow.draw(); //delegation
    }
    public String getDescription() {
        return decoratedWindow.getDescription(); //delegation
    }
}
 
//az első konkrét díszítő, amely vízszintes görgetősáv funkcionalitást ad hozzá
class VerticalScrollBarDecorator extends WindowDecorator {
    public VerticalScrollBarDecorator (Window decoratedWindow) {
        super(decoratedWindow);
    }
 
    @Override
    public void draw() {
        super.draw();
        drawVerticalScrollBar();
    }
 
    private void drawVerticalScrollBar() {
        // draw the vertical scrollbar
    }
 
    @Override
    public String getDescription() {
        return super.getDescription() + ", including vertical scrollbars";
    }
}
 
//a második konkrét díszítő, amely horizontális görgetősáv funkcionalitást ad hozzá
class HorizontalScrollBarDecorator extends WindowDecorator {
    public HorizontalScrollBarDecorator (Window decoratedWindow) {
        super(decoratedWindow);
    }
 
    @Override
    public void draw() {
        super.draw();
        drawHorizontalScrollBar();
    }
 
    private void drawHorizontalScrollBar() {
        // draw the horizontal scrollbar
    }
 
    @Override
    public String getDescription() {
        return super.getDescription() + ", including horizontal scrollbars";
    }
}

Íme a teszt program, ami létrehoz egy olyan Window példányt, amely teljesen díszített (azaz vízszintes és függőleges görgetősávval rendelkezik), és kiírja a leírásukat:

public class DecoratedWindowTest {
    public static void main(String[] args) {
        // create a decorated Window with horizontal and vertical scrollbars
        Window decoratedWindow = new HorizontalScrollBarDecorator (
                new VerticalScrollBarDecorator(new SimpleWindow()));
 
        // print the Window's description
        System.out.println(decoratedWindow.getDescription());
    }
}

Ezen program kimenete a következő: "simple window, including vertical scrollbars, including horizontal scrollbars". Figyeljük meg, hogy a getDescription metódusa a két díszítőnek először visszaadja a díszített Window leírását, és azt díszíti egy utótaggal.

Második példa (kávékészítés esete)[szerkesztés | forrásszöveg szerkesztése]

A következő Java példa a díszítők használatát szemlélteti a kávékészítés esetén. Ebben a példában az esetünk csak a költségeket és a hozzávalókat tartalmazza.

// The abstract Coffee class defines the functionality of Coffee implemented by decorator
public abstract class Coffee {
    public abstract double getCost(); // returns the cost of the coffee
    public abstract String getIngredients(); // returns the ingredients of the coffee
}
 
// extension of a simple coffee without any extra ingredients
public class SimpleCoffee extends Coffee {
    public double getCost() {
        return 1;
    }
 
    public String getIngredients() {
        return "Coffee";
    }
}

A következő osztályok tartalmazzák a díszítőket az összes Coffee(kávé) osztályra, beleértve a díszítő osztályokat magukat.

// absztrakt díszítő osztály, amely kiterjeszti a Coffee absztrakt osztályt
public abstract class CoffeeDecorator extends Coffee {
    protected final Coffee decoratedCoffee;
    protected String ingredientSeparator = ", ";
 
    public CoffeeDecorator(Coffee decoratedCoffee) {
        this.decoratedCoffee = decoratedCoffee;
    }
 
    public double getCost() { // implementing methods of the abstract class
        return decoratedCoffee.getCost();
    }
 
    public String getIngredients() {
        return decoratedCoffee.getIngredients();
    }
}
 
// Decorator Milk that mixes milk with coffee
// note it extends CoffeeDecorator
class Milk extends CoffeeDecorator {
    public Milk(Coffee decoratedCoffee) {
        super(decoratedCoffee);
    }
 
    public double getCost() { // overriding methods defined in the abstract superclass
        return super.getCost() + 0.5;
    }
 
    public String getIngredients() {
        return super.getIngredients() + ingredientSeparator + "Milk";
    }
}
 
// Decorator Whip that mixes whip with coffee
// note it extends CoffeeDecorator
class Whip extends CoffeeDecorator {
    public Whip(Coffee decoratedCoffee) {
        super(decoratedCoffee);
    }
 
    public double getCost() {
        return super.getCost() + 0.7;
    }
 
    public String getIngredients() {
        return super.getIngredients() + ingredientSeparator + "Whip";
    }
}
 
// Decorator Sprinkles that mixes sprinkles with coffee
// note it extends CoffeeDecorator
class Sprinkles extends CoffeeDecorator {
    public Sprinkles(Coffee decoratedCoffee) {
        super(decoratedCoffee);
    }
 
    public double getCost() {
        return super.getCost() + 0.2;
    }
 
    public String getIngredients() {
        return super.getIngredients() + ingredientSeparator + "Sprinkles";
    }
}

Íme a tesztprogram, amely létrehoz egy Coffee példányt, amely teljes mértékben díszített (azaz tejjel, habbal, permettel) és kiszámolja kávé költségét és kiírja a hozzávalóit:

public class Main {
 
    public static final void main(String[] args) {
	Coffee c = new SimpleCoffee();
	System.out.println("Cost: " + c.getCost() + "; Ingredients: " + c.getIngredients());
 
	c = new Milk(c);
	System.out.println("Cost: " + c.getCost() + "; Ingredients: " + c.getIngredients());
 
	c = new Sprinkles(c);
	System.out.println("Cost: " + c.getCost() + "; Ingredients: " + c.getIngredients());
 
	c = new Whip(c);
	System.out.println("Cost: " + c.getCost() + "; Ingredients: " + c.getIngredients());
 
	// Note that you can also stack more than one decorator of the same type
	c = new Sprinkles(c);
	System.out.println("Cost: " + c.getCost() + "; Ingredients: " + c.getIngredients());
    }
 
}

C++[szerkesztés | forrásszöveg szerkesztése]

Következő példa program egy C++-os megvalósítás:

Include-ok

#include <iostream>
#include <string>

Absztrakt alaposztályok

// Az absztrakt Coffee (kávé) osztály, amely definiálja a Coffee funkcionalitását, melyet a díszítő fog megvalósítani 
struct Coffee {
  virtual double getCost() = 0; // returns the cost of the coffee
  virtual std::string getIngredients() = 0; // returns the ingredients of the coffee
  virtual ~Coffee() = 0;
};
inline Coffee::~Coffee(){}

SimpleCoffee osztály.

// egy egyszerű kávé kiterjesztése extra hozzávalók nélkül
struct SimpleCoffee : Coffee {
  double getCost() {
    return 1.0;
  }
 
  std::string getIngredients() {
    return "Coffee";
  }
};

Díszítők

 
// Milk (tej) díszítő, amely tejet ad hozzá kávéhoz
struct MilkDecorator : Coffee {
public: 
  MilkDecorator(Coffee* basicCoffee)
    :basicCoffee_(basicCoffee) {
  }
 
  double getCost() { // providing methods defined in the abstract superclass
    return basicCoffee_->getCost() + 0.5;
  }
 
  std::string getIngredients() {
    return basicCoffee_->getIngredients() + ", " + "Milk";
  }
private:
  Coffee* basicCoffee_;
};
 
// Decorator Whip that adds whip to coffee
struct WhipDecorator : Coffee {
public:
  WhipDecorator(Coffee* basicCoffee)
    :basicCoffee_(basicCoffee) {
  }
 
  double getCost() {
    return basicCoffee_->getCost() + 0.7;
  }
 
  std::string getIngredients() {
    return basicCoffee_->getIngredients() + ", " + "Whip";
  }
private:
  Coffee* basicCoffee_;
 
};

Tesztprogram

int main() 
{
  SimpleCoffee s;
  std::cout << "Cost: " << s.getCost() << "; Ingredients: " << s.getIngredients() << std::endl;
 
  MilkDecorator m(&s);
  std::cout << "Cost: " << m.getCost() << "; Ingredients: " << m.getIngredients() << std::endl;
 
  WhipDecorator w(&s);
  std::cout << "Cost: " << w.getCost() << "; Ingredients: " << w.getIngredients() << std::endl;
 
  // Note that you can stack decorators:
  MilkDecorator m2(&w);
  std::cout << "Cost: " << m2.getCost() << "; Ingredients: " << m2.getIngredients() << std::endl;
}

A program kimenete:

Cost: 1.0; Ingredients: Coffee
Cost: 1.5; Ingredients: Coffee, Milk
Cost: 1.7; Ingredients: Coffee, Whip
Cost: 2.2; Ingredients: Coffee, Whip, Milk

Dinamikus nyelvek[szerkesztés | forrásszöveg szerkesztése]

A díszítő minta megvalósítható a dinamikus nyelvekkel, akár interfészekkel, akár OOP-ben hagyományos öröklődéssel.

Kapcsolódó szócikkek[szerkesztés | forrásszöveg szerkesztése]

Jegyzetek[szerkesztés | forrásszöveg szerkesztése]

  1. Gamma, et. al., Erich. Design Patterns. Reading, MA: Addison-Wesley Publishing Co, Inc., 175ff. o (1995). ISBN 0-201-63361-2 

További információk[szerkesztés | forrásszöveg szerkesztése]

Fordítás[szerkesztés | forrásszöveg szerkesztése]

Ez a szócikk részben vagy egészben a Decorator pattern című angol Wikipédia-szócikk ezen változatának fordításán alapul. Az eredeti cikk szerkesztőit annak laptörténete sorolja fel.