Dekorator wzór - Decorator pattern

W programowaniu obiektowym , wzór dekorator jest wzorzec projektowy , który umożliwia zachowanie do dodania do pojedynczego obiektu , dynamicznie, bez wpływu na zachowanie innych obiektów z tej samej klasy . Wzór dekoratora jest często przydatny do przestrzegania zasady pojedynczej odpowiedzialności , ponieważ umożliwia podział funkcjonalności między klasy o unikalnych obszarach zainteresowania. Użycie dekoratora może być bardziej efektywne niż tworzenie klas podrzędnych, ponieważ zachowanie obiektu można rozszerzyć bez definiowania zupełnie nowego obiektu.

Przegląd

Dekorator wzór jest to jeden z dobrze znanych dwudziestu trzech wzorów GOF konstrukcyjnych ; opisują one, jak rozwiązywać powtarzające się problemy projektowe i projektować elastyczne i wielokrotnego użytku oprogramowanie zorientowane obiektowo – to znaczy obiekty, które są łatwiejsze do wdrożenia, zmiany, testowania i ponownego użycia.

Jakie problemy może rozwiązać?

  • Obowiązki powinny być dodawane (i usuwane z) obiektu dynamicznie w czasie wykonywania.
  • Należy zapewnić elastyczną alternatywę dla podklasowania w celu rozszerzenia funkcjonalności.

Podczas korzystania z podklas różne podklasy rozszerzają klasę na różne sposoby. Ale rozszerzenie jest powiązane z klasą w czasie kompilacji i nie można go zmienić w czasie wykonywania.

Jakie rozwiązanie opisuje?

Zdefiniuj Decoratorobiekty, które

  • zaimplementuj interfejs rozszerzonego (dekorowanego) obiektu ( Component) w sposób przezroczysty, przekazując do niego wszystkie żądania
  • wykonać dodatkowe funkcje przed/po przesłaniu żądania.

Umożliwia to pracę z różnymi Decoratorobiektami w celu dynamicznego rozszerzenia funkcjonalności obiektu w czasie wykonywania.
Zobacz także diagram klas i sekwencji UML poniżej.

Zamiar

Diagram klasy dekoratora UML

Wzorzec dekoratora może być używany do rozszerzania (dekorowania) funkcjonalności określonego obiektu statycznie lub w niektórych przypadkach w czasie wykonywania , niezależnie od innych wystąpień tej samej klasy , pod warunkiem wykonania pewnych prac przygotowawczych w czasie projektowania. Osiąga się to poprzez zaprojektowanie nowej klasy Decorator, która otacza oryginalną klasę. Zawijanie to można osiągnąć, wykonując następującą sekwencję kroków:

  1. Podklasę oryginalną klasę Component do klasy Decorator (patrz diagram UML);
  2. W klasie Decorator dodaj wskaźnik Component jako pole;
  3. W klasie Decorator przekaż Component do konstruktora Decorator, aby zainicjować wskaźnik Component ;
  4. W klasie Decorator przekaż wszystkie metody Component do wskaźnika Component ; oraz
  5. W klasie ConcreteDecorator nadpisz wszystkie metody Component, których zachowanie wymaga modyfikacji.

Ten wzór został zaprojektowany tak, aby wiele dekoratorów można było ułożyć jeden na drugim, za każdym razem dodając nową funkcjonalność do zastąpionych metod.

Zauważ, że dekoratory i oryginalny obiekt klasy mają wspólny zestaw funkcji. Na poprzednim schemacie metoda operation() była dostępna zarówno w wersji dekorowanej, jak i niedekorowanej.

Cechy dekoracji (np. metody, właściwości lub inne elementy) są zwykle definiowane przez interfejs, mixin (aka cecha ) lub dziedziczenie klasy, które jest wspólne dla dekoratorów i dekorowanego obiektu. W poprzednim przykładzie klasa Component jest dziedziczona zarówno przez ConcreteComponent, jak i podklasy, które pochodzą od Decorator .

Wzór dekoratora jest alternatywą dla podklasy . Tworzenie podklas dodaje zachowanie w czasie kompilacji , a zmiana wpływa na wszystkie wystąpienia oryginalnej klasy; dekorowanie może zapewnić nowe zachowanie w czasie wykonywania dla wybranych obiektów.

Ta różnica staje się najważniejsza, gdy istnieje kilka niezależnych sposobów rozszerzania funkcjonalności. W niektórych obiektowych językach programowania klas nie można tworzyć w czasie wykonywania i zazwyczaj nie można przewidzieć w czasie projektowania, jakie kombinacje rozszerzeń będą potrzebne. Oznaczałoby to, że dla każdej możliwej kombinacji należałoby stworzyć nową klasę. Natomiast dekoratory to obiekty tworzone w czasie wykonywania i można je łączyć na podstawie użycia. Implementacje strumieni we/wy zarówno Java, jak i .NET Framework zawierają wzorzec dekoratora.

Motywacja

Diagram UML dla przykładu okna

Jako przykład rozważmy okno w systemie okienkowym . Aby umożliwić przewijanie zawartości okna, można dodać do niego odpowiednio poziome lub pionowe paski przewijania . Załóżmy, że okna są reprezentowane przez instancje interfejsu Window i załóżmy, że ta klasa nie ma funkcji dodawania pasków przewijania. Można utworzyć podklasę ScrollingWindow, która je udostępnia, lub utworzyć ScrollingWindowDecorator, która dodaje tę funkcjonalność do istniejących obiektów Window . W tym momencie każde rozwiązanie byłoby w porządku.

Załóżmy teraz, że chce się również możliwość dodawania obramowań do okien. Ponownie, oryginalna klasa Window nie ma obsługi. ScrollingWindow podklasa teraz stanowi problem, ponieważ skutecznie stworzył nowy rodzaj okna. Jeśli ktoś chce dodać obsługę obramowania do wielu okien, ale nie do wszystkich , musi stworzyć podklasy WindowWithBorder i ScrollingWindowWithBorder , itp. Problem ten pogarsza się z każdą nową funkcją lub podtypem okna, które mają zostać dodane. Dla rozwiązania dekoratora tworzony jest nowy BorderedWindowDecorator . Dowolna kombinacja ScrollingWindowDecorator lub BorderedWindowDecorator może ozdobić istniejące okna. Jeśli funkcjonalność musi zostać dodana do wszystkich systemów Windows, można zmodyfikować klasę bazową. Z drugiej strony czasami (np. przy użyciu zewnętrznych frameworków) modyfikacja klasy bazowej nie jest możliwa, legalna lub wygodna.

W poprzednim przykładzie klasy SimpleWindow i WindowDecorator implementują interfejs Window , który definiuje metodę draw() i metodę getDescription() , które są wymagane w tym scenariuszu w celu dekoracji kontrolki okna.

Typowe przypadki użycia

Stosowanie dekoratorów

Dodawanie lub usuwanie dekoratorów na polecenie (takie jak naciśnięcie przycisku) jest powszechnym wzorcem interfejsu użytkownika, często implementowanym wraz z wzorcem projektowym Command . Na przykład aplikacja do edycji tekstu może mieć przycisk do podświetlania tekstu. Po naciśnięciu przycisku, poszczególne aktualnie wybrane glify tekstowe zostaną opakowane w dekoratory, które modyfikują ich funkcję draw(), powodując, że zostaną narysowane w podświetlony sposób (prawdziwa implementacja prawdopodobnie również korzystałaby z systemu demarkacji, aby zmaksymalizować wydajność).

Innym powszechnym przypadkiem użycia jest stosowanie lub usuwanie dekoratorów na podstawie zmian stanu. W zależności od stanu, dekoratory można nakładać lub usuwać luzem. Podobnie wzorzec projektowy State można zaimplementować przy użyciu dekoratorów zamiast obiektów podklasowych zawierających zmieniającą się funkcjonalność. Użycie dekoratorów w ten sposób sprawia, że ​​wewnętrzny stan i funkcjonalność obiektu State są bardziej kompozycyjne i zdolne do obsługi dowolnej złożoności.

Zastosowanie w obiektach Flyweight

Dekoracja jest również często stosowana we wzorcu projektowym Flyweight . Obiekty typu flyweight są podzielone na dwa składniki: niezmienny składnik, który jest współdzielony przez wszystkie obiekty typu flyweight; oraz wariant, dekorowany element, który może być częściowo dzielony lub całkowicie nie dzielony. To partycjonowanie obiektu flyweight ma na celu zmniejszenie zużycia pamięci. Dekoratory są zwykle buforowane i ponownie używane. Wszystkie dekoratory będą zawierać wspólne odniesienie do wspólnego, niezmiennego obiektu. Jeśli stan dekorowania jest tylko częściowo wariantem, dekoratory mogą być również do pewnego stopnia współdzielone – chociaż należy uważać, aby nie zmieniać ich stanu podczas ich używania. UITableView systemu iOS implementuje wzorzec flyweight w ten sposób — komórki wielokrotnego użytku widoku tabeli są dekoratorami, które zawierają odniesienia do wspólnego obiektu wiersza widoku tabeli, a komórki są buforowane / ponownie używane.

Przeszkody w kontaktach z dekoratorami

Stosowanie kombinacji dekoratorów na różne sposoby do kolekcji obiektów wprowadza pewne problemy w łączeniu się z kolekcją w sposób, który w pełni wykorzystuje funkcjonalność dodaną przez dekoratorów. W takich przypadkach przydatne może być użycie wzorców Adapter lub Visitor . Współpraca z wieloma warstwami dekoratorów stwarza dodatkowe wyzwania, a logika adapterów i gości musi być zaprojektowana, aby to uwzględnić.

Znaczenie architektoniczne

Dekoratorzy wspierają kompozycyjne raczej odgórne, hierarchiczne podejście do rozszerzania funkcjonalności. Dekorator umożliwia dodanie lub zmianę zachowania interfejsu w czasie wykonywania. Mogą być używane do owijania obiektów w wielowarstwową, arbitralną kombinację sposobów. Robienie tego samego z podklasami oznacza implementację złożonych sieci wielokrotnego dziedziczenia, które są niewydajne pod względem pamięci iw pewnym momencie po prostu nie mogą być skalowane. Podobnie próba zaimplementowania tej samej funkcji z właściwościami powoduje rozdęcie każdego wystąpienia obiektu o niepotrzebne właściwości. Z powyższych powodów dekoratory są często uważane za wydajną pamięć alternatywę dla podklas.

Dekoratory mogą być również używane do specjalizowania obiektów, których nie można podklasować, których cechy należy zmienić w czasie wykonywania (jak wspomniano w innym miejscu) lub ogólnie obiektów, które nie mają jakiejś potrzebnej funkcjonalności.

Wykorzystanie w ulepszaniu interfejsów API

Wzór dekoratora może również wzmocnić wzór elewacji . Fasada jest zaprojektowana tak, aby po prostu łączyć się ze złożonym systemem, który obejmuje, ale nie dodaje funkcjonalności do systemu. Jednak opakowanie złożonego systemu zapewnia przestrzeń, którą można wykorzystać do wprowadzenia nowej funkcjonalności w oparciu o koordynację podkomponentów w systemie. Na przykład wzór elewacji może ujednolicić wiele różnych słowników językowych w ramach jednego wielojęzycznego interfejsu słownika. Nowy interfejs może również oferować nowe funkcje tłumaczenia słów między językami. To hybrydowy wzorzec – ujednolicony interfejs zapewnia przestrzeń do augmentacji. Pomyśl o dekoratorach jako nie ograniczających się do owijania pojedynczych obiektów, ale zdolnych do owijania klastrów obiektów również w tym hybrydowym podejściu.

Alternatywy dla dekoratorów

Jako alternatywę do wzoru dekorator The Adapter może być stosowane, gdy owijka musi respektować poszczególnego złącza i musi obsługiwać polimorficzne zachowanie i elewacji , gdy pożądane jest łatwiejsze i prostsze interfejs do bazowego obiektu.

Wzór Zamiar
Adapter Konwertuje jeden interfejs na inny, aby odpowiadał oczekiwaniom klienta
Dekorator Dynamicznie dodaje odpowiedzialność do interfejsu, owijając oryginalny kod
Fasada Zapewnia uproszczony interfejs

Struktura

Diagram klas i sekwencji UML

Przykładowy diagram klas i sekwencji UML dla wzorca projektowego Dekorator.

Na powyższym diagramie klas UML, klasa abstrakcyjna Decoratorutrzymuje referencję ( component) do dekorowanego obiektu ( Component) i przekazuje do niego wszystkie żądania ( component.operation()). To sprawia, że ​​są one Decoratorprzezroczyste (niewidoczne) dla klientów Component.

Podklasy ( Decorator1, Decorator2) implementują dodatkowe zachowanie ( addBehavior()), które należy dodać do Component(przed/po przesłaniu do niego żądania).
Diagram sekwencji pokazuje interakcje w czasie wykonywania: ClientObiekt działa poprzez obiekty Decorator1i Decorator2rozszerza ich funkcjonalność Component1.
Do Clientrozmowy operation() na temat Decorator1, który przekazuje żądanie do Decorator2. Decorator2wykonuje addBehavior()po przesłaniu żądania Component1i powraca do Decorator1, który wykonuje addBehavior() i powraca do Client.

Przykłady

Udać się

package decolog

import (
	"log"
	"time"
)

//OperateFn represents operations that require decoration
type OperateFn func()

//Decorate the operation
func Decorate(opFn OperateFn)  {
	defer func(s time.Time) {
		log.Printf("elapsed time %0.2d ms", time.Since(s).Nanoseconds()/(1<<20))
	}(time.Now())

	// real operation function
	opFn()
}

// package main
package main

import (
	"github.com/tkstorm/go-design/structural/decorator/decolog"
	"log"
	"math/rand"
	"time"
)

//output:
//2019/08/19 19:05:24 finish action a
//2019/08/19 19:05:24 elapsed time 77 ms
//2019/08/19 19:05:24 finish action b
//2019/08/19 19:05:24 elapsed time 88 ms
func main() {
	// decorate log a
	decolog.Decorate(decolog.OperateFn(DoActionA))
	// decorate log b
	decolog.Decorate(decolog.OperateFn(DoActionB))
}

func DoActionA() {
	time.Sleep(time.Duration(rand.Intn(200)) * time.Millisecond)
	log.Println("finish action a")
}

func DoActionB() {
	time.Sleep(time.Duration(rand.Intn(200)) * time.Millisecond)
	log.Println("finish action b")
}

C++

Przedstawione są tutaj dwie opcje: po pierwsze, dynamiczny dekorator, który można komponować w czasie wykonywania (ma problemy z wywoływaniem funkcji dekorowanych, chyba że jest to jawne proxy) oraz dekorator, który używa dziedziczenia mixin.

Dynamiczny dekorator

#include <iostream>
#include <string>

struct Shape {
  virtual ~Shape() = default;

  virtual std::string GetName() const = 0;
};

struct Circle : Shape {
  void Resize(float factor) { radius *= factor; }

  std::string GetName() const override {
    return std::string("A circle of radius ") + std::to_string(radius);
  }

  float radius = 10.0f;
};

struct ColoredShape : Shape {
  ColoredShape(const std::string& color, Shape* shape)
      : color(color), shape(shape) {}

  std::string GetName() const override {
    return shape->GetName() + " which is colored " + color;
  }

  std::string color;
  Shape* shape;
};

int main() {
  Circle circle;
  ColoredShape colored_shape("red", &circle);
  std::cout << colored_shape.GetName() << std::endl;

}
#include <memory>
#include <iostream>
#include <string>

struct WebPage
{
    virtual void display()=0;
    virtual ~WebPage() = default;
};

struct BasicWebPage : WebPage
{
    std::string html;
    void display() override
    {
        std::cout << "Basic WEB page" << std::endl;
    }
};

struct WebPageDecorator : WebPage
{
    WebPageDecorator(std::unique_ptr<WebPage> webPage): _webPage(std::move(webPage))
    {
    }
    void display() override
    {
        _webPage->display();
    }
private:
    std::unique_ptr<WebPage> _webPage;
};

struct AuthenticatedWebPage : WebPageDecorator
{
    AuthenticatedWebPage(std::unique_ptr<WebPage> webPage): 
    WebPageDecorator(std::move(webPage))
    {}

    void authenticateUser()
    {
        std::cout << "authentification done" << std::endl;
    }
    void display() override
    {
        authenticateUser();
        WebPageDecorator::display();
    }
};

struct AuthorizedWebPage : WebPageDecorator
{
    AuthorizedWebPage(std::unique_ptr<WebPage> webPage): 
    WebPageDecorator(std::move(webPage))
    {}

    void authorizedUser()
    {
        std::cout << "authorized done" << std::endl;
    }
    void display() override
    {
        authorizedUser();
        WebPageDecorator::display();
    }
};

int main(int argc, char* argv[])
{
    std::unique_ptr<WebPage> myPage = std::make_unique<BasicWebPage>();

    myPage = std::make_unique<AuthorizedWebPage>(std::move(myPage));
    myPage = std::make_unique<AuthenticatedWebPage>(std::move(myPage));
    myPage->display();
    std::cout << std::endl;
    return 0;
}

Dekorator statyczny (Dziedziczenie mixinów)

Ten przykład ilustruje statyczną implementację dekoratora, która jest możliwa dzięki możliwości C++ dziedziczenia z argumentu szablonu.

#include <iostream>
#include <string>

struct Circle {
  void Resize(float factor) { radius *= factor; }

  std::string GetName() const {
    return std::string("A circle of radius ") + std::to_string(radius);
  }

  float radius = 10.0f;
};

template <typename T>
struct ColoredShape : public T {
  ColoredShape(const std::string& color) : color(color) {}

  std::string GetName() const {
    return T::GetName() + " which is colored " + color;
  }

  std::string color;
};

int main() {
  ColoredShape<Circle> red_circle("red");
  std::cout << red_circle.GetName() << std::endl;
  red_circle.Resize(1.5f);
  std::cout << red_circle.GetName() << std::endl;
}

Jawa

Pierwszy przykład (scenariusz z oknem/przewijaniem)

Poniższy przykład Java ilustruje użycie dekoratorów przy użyciu scenariusza okna/przewijania.

// The Window interface class
public interface Window {
    void draw(); // Draws the Window
    String getDescription(); // Returns a description of the Window
}

// Implementation of a simple Window without any scrollbars
class SimpleWindow implements Window {
    @Override
    public void draw() {
        // Draw window
    }
    @Override
    public String getDescription() {
        return "simple window";
    }
}

Poniższe klasy zawierają dekoratory dla wszystkich Windowklas, w tym same klasy dekoratorów.

// abstract decorator class - note that it implements Window
abstract class WindowDecorator implements Window {
    private final Window windowToBeDecorated; // the Window being decorated

    public WindowDecorator (Window windowToBeDecorated) {
        this.windowToBeDecorated = windowToBeDecorated;
    }
    @Override
    public void draw() {
        windowToBeDecorated.draw(); //Delegation
    }
    @Override
    public String getDescription() {
        return windowToBeDecorated.getDescription(); //Delegation
    }
}

// The first concrete decorator which adds vertical scrollbar functionality
class VerticalScrollBarDecorator extends WindowDecorator {
    public VerticalScrollBarDecorator (Window windowToBeDecorated) {
        super(windowToBeDecorated);
    }

    @Override
    public void draw() {
        super.draw();
        drawVerticalScrollBar();
    }

    private void drawVerticalScrollBar() {
        // Draw the vertical scrollbar
    }

    @Override
    public String getDescription() {
        return super.getDescription() + ", including vertical scrollbars";
    }
}

// The second concrete decorator which adds horizontal scrollbar functionality
class HorizontalScrollBarDecorator extends WindowDecorator {
    public HorizontalScrollBarDecorator (Window windowToBeDecorated) {
        super(windowToBeDecorated);
    }

    @Override
    public void draw() {
        super.draw();
        drawHorizontalScrollBar();
    }

    private void drawHorizontalScrollBar() {
        // Draw the horizontal scrollbar
    }

    @Override
    public String getDescription() {
        return super.getDescription() + ", including horizontal scrollbars";
    }
}

Oto program testowy, który tworzy Windowinstancję, która jest w pełni ozdobiona (tj. z pionowymi i poziomymi paskami przewijania) i wyświetla jej opis:

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());
    }
}

Wynikiem tego programu jest "proste okno, w tym pionowe paski przewijania, w tym poziome paski przewijania". Zwróć uwagę, jak getDescriptionmetoda dwóch dekoratorów najpierw pobiera Windowopis ozdobionego elementu i ozdabia go sufiksem.

Poniżej znajduje się klasa testowa JUnit dla Test Driven Development

import static org.junit.Assert.assertEquals;

import org.junit.Test;

public class WindowDecoratorTest {
	@Test
	public void testWindowDecoratorTest() {
	    Window decoratedWindow = new HorizontalScrollBarDecorator(new VerticalScrollBarDecorator(new SimpleWindow()));
      	    // assert that the description indeed includes horizontal + vertical scrollbars
            assertEquals("simple window, including vertical scrollbars, including horizontal scrollbars", decoratedWindow.getDescription());
	}
}


Drugi przykład (scenariusz parzenia kawy)

Kolejny przykład w Javie ilustruje użycie dekoratorów przy użyciu scenariusza parzenia kawy. W tym przykładzie scenariusz obejmuje tylko koszt i składniki.

// The interface Coffee defines the functionality of Coffee implemented by decorator
public interface Coffee {
    public double getCost(); // Returns the cost of the coffee
    public String getIngredients(); // Returns the ingredients of the coffee
}

// Extension of a simple coffee without any extra ingredients
public class SimpleCoffee implements Coffee {
    @Override
    public double getCost() {
        return 1;
    }

    @Override
    public String getIngredients() {
        return "Coffee";
    }
}

Poniższe klasy zawierają dekoratory dla wszystkich klas Coffee , w tym same klasy dekoratorów.

// Abstract decorator class - note that it implements Coffee interface
public abstract class CoffeeDecorator implements Coffee {
    private final Coffee decoratedCoffee;

    public CoffeeDecorator(Coffee c) {
        this.decoratedCoffee = c;
    }

    @Override
    public double getCost() { // Implementing methods of the interface
        return decoratedCoffee.getCost();
    }

    @Override
    public String getIngredients() {
        return decoratedCoffee.getIngredients();
    }
}

// Decorator WithMilk mixes milk into coffee.
// Note it extends CoffeeDecorator.
class WithMilk extends CoffeeDecorator {
    public WithMilk(Coffee c) {
        super(c);
    }

    @Override
    public double getCost() { // Overriding methods defined in the abstract superclass
        return super.getCost() + 0.5;
    }

    @Override
    public String getIngredients() {
        return super.getIngredients() + ", Milk";
    }
}

// Decorator WithSprinkles mixes sprinkles onto coffee.
// Note it extends CoffeeDecorator.
class WithSprinkles extends CoffeeDecorator {
    public WithSprinkles(Coffee c) {
        super(c);
    }

    @Override
    public double getCost() {
        return super.getCost() + 0.2;
    }

    @Override
    public String getIngredients() {
        return super.getIngredients() + ", Sprinkles";
    }
}

Oto program testowy, który tworzy instancję Coffee, która jest w pełni ozdobiona (z mlekiem i posypką), oblicza koszt kawy i drukuje jej składniki:

public class Main {
    public static void printInfo(Coffee c) {
        System.out.println("Cost: " + c.getCost() + "; Ingredients: " + c.getIngredients());
    }

    public static void main(String[] args) {
        Coffee c = new SimpleCoffee();
        printInfo(c);

        c = new WithMilk(c);
        printInfo(c);

        c = new WithSprinkles(c);
        printInfo(c);
    }
}

Wynik działania tego programu podano poniżej:

Cost: 1.0; Ingredients: Coffee
Cost: 1.5; Ingredients: Coffee, Milk
Cost: 1.7; Ingredients: Coffee, Milk, Sprinkles

PHP

abstract class Component
{
    protected $data;
    protected $value;

    abstract public function getData();

    abstract public function getValue();
}

class ConcreteComponent extends Component
{
    public function __construct()
    {
        $this->value = 1000;
        $this->data = "Concrete Component:\t{$this->value}\n";
    }

    public function getData()
    {
        return $this->data;
    }

    public function getValue()
    {
        return $this->value;
    }
}

abstract class Decorator extends Component
{
    
}

class ConcreteDecorator1 extends Decorator
{
    public function __construct(Component $data)
    {
        $this->value = 500;
        $this->data = $data;
    }

    public function getData()
    {
        return $this->data->getData() . "Concrete Decorator 1:\t{$this->value}\n";
    }

    public function getValue()
    {
        return $this->value + $this->data->getValue();
    }
}

class ConcreteDecorator2 extends Decorator
{
    public function __construct(Component $data)
    {
        $this->value = 500;
        $this->data = $data;
    }

    public function getData()
    {
        return $this->data->getData() . "Concrete Decorator 2:\t{$this->value}\n";
    }

    public function getValue()
    {
        return $this->value + $this->data->getValue();
    }
}

class Client
{
    private $component;

    public function __construct()
    {
        $this->component = new ConcreteComponent();
        $this->component = $this->wrapComponent($this->component);

        echo $this->component->getData();
        echo "Client:\t\t\t";
        echo $this->component->getValue();
    }

    private function wrapComponent(Component $component)
    {
        $component1 = new ConcreteDecorator1($component);
        $component2 = new ConcreteDecorator2($component1);
        return $component2;
    }
}

$client = new Client();

// Result: #quanton81

//Concrete Component:	1000
//Concrete Decorator 1:	500
//Concrete Decorator 2:	500
//Client:               2000

Pyton

Poniższy przykład Pythona, zaczerpnięty z Python Wiki - DecoratorPattern , pokazuje nam, jak za pomocą dekoratorów potoku dynamicznie dodawać wiele zachowań w obiekcie:

"""
Demonstrated decorators in a world of a 10x10 grid of values 0-255. 
"""

import random


def s32_to_u16(x):
    if x < 0:
        sign = 0xF000
    else:
        sign = 0
    bottom = x & 0x00007FFF
    return bottom | sign


def seed_from_xy(x, y):
    return s32_to_u16(x) | (s32_to_u16(y) << 16)


class RandomSquare:
    def __init__(s, seed_modifier):
        s.seed_modifier = seed_modifier

    def get(s, x, y):
        seed = seed_from_xy(x, y) ^ s.seed_modifier
        random.seed(seed)
        return random.randint(0, 255)


class DataSquare:
    def __init__(s, initial_value=None):
        s.data = [initial_value] * 10 * 10

    def get(s, x, y):
        return s.data[(y * 10) + x]  # yes: these are all 10x10

    def set(s, x, y, u):
        s.data[(y * 10) + x] = u


class CacheDecorator:
    def __init__(s, decorated):
        s.decorated = decorated
        s.cache = DataSquare()

    def get(s, x, y):
        if s.cache.get(x, y) == None:
            s.cache.set(x, y, s.decorated.get(x, y))
        return s.cache.get(x, y)


class MaxDecorator:
    def __init__(s, decorated, max):
        s.decorated = decorated
        s.max = max

    def get(s, x, y):
        if s.decorated.get(x, y) > s.max:
            return s.max
        return s.decorated.get(x, y)


class MinDecorator:
    def __init__(s, decorated, min):
        s.decorated = decorated
        s.min = min

    def get(s, x, y):
        if s.decorated.get(x, y) < s.min:
            return s.min
        return s.decorated.get(x, y)


class VisibilityDecorator:
    def __init__(s, decorated):
        s.decorated = decorated

    def get(s, x, y):
        return s.decorated.get(x, y)

    def draw(s):
        for y in range(10):
            for x in range(10):
                print "%3d" % s.get(x, y),
            print


# Now, build up a pipeline of decorators:

random_square = RandomSquare(635)
random_cache = CacheDecorator(random_square)
max_filtered = MaxDecorator(random_cache, 200)
min_filtered = MinDecorator(max_filtered, 100)
final = VisibilityDecorator(min_filtered)

final.draw()

Notatka:

Proszę nie mylić wzorca dekoratora (lub implementacji tego wzorca projektowego w Pythonie - jak w powyższym przykładzie) z dekoratorami Pythona , funkcją języka Python. To różne rzeczy.

Po drugie do Wiki Pythona:

Wzorzec dekoratora to wzorzec opisany w Księdze Wzorców Projektowych. Jest to sposób na pozorną modyfikację zachowania obiektu poprzez zamknięcie go wewnątrz obiektu dekorującego o podobnym interfejsie. Nie należy tego mylić z dekoratorami Pythona, które są funkcją języka do dynamicznego modyfikowania funkcji lub klasy.

Kryształ

abstract class Coffee
  abstract def cost
  abstract def ingredients
end

# Extension of a simple coffee
class SimpleCoffee < Coffee
  def cost
    1.0
  end

  def ingredients
    "Coffee"
  end
end

# Abstract decorator
class CoffeeDecorator < Coffee
  protected getter decorated_coffee : Coffee

  def initialize(@decorated_coffee)
  end

  def cost
    decorated_coffee.cost
  end

  def ingredients
    decorated_coffee.ingredients
  end
end

class WithMilk < CoffeeDecorator
  def cost
    super + 0.5
  end

  def ingredients
    super + ", Milk"
  end
end

class WithSprinkles < CoffeeDecorator
  def cost
    super + 0.2
  end

  def ingredients
    super + ", Sprinkles"
  end
end

class Program
  def print(coffee : Coffee)
    puts "Cost: #{coffee.cost}; Ingredients: #{coffee.ingredients}"
  end

  def initialize
    coffee = SimpleCoffee.new
    print(coffee)

    coffee = WithMilk.new(coffee)
    print(coffee)

    coffee = WithSprinkles.new(coffee)
    print(coffee)
  end
end

Program.new

Wyjście:

Cost: 1.0; Ingredients: Coffee
Cost: 1.5; Ingredients: Coffee, Milk
Cost: 1.7; Ingredients: Coffee, Milk, Sprinkles

C#

namespace WikiDesignPatterns
{
    public interface IBike
    {
        string GetDetails();
        double GetPrice();
    }

    public class AluminiumBike : IBike
    {
        public double GetPrice() =>
            100.0;

        public string GetDetails() =>
            "Aluminium Bike";
    }

    public class CarbonBike : IBike
    {
        public double GetPrice() =>
            1000.0;

        public string GetDetails() =>
            "Carbon";
    }


    public abstract class BikeAccessories : IBike
    {
        private readonly IBike _bike;

        public BikeAccessories(IBike bike)
        {
            _bike = bike;
        }

        public virtual double GetPrice() =>
            _bike.GetPrice();


        public virtual string GetDetails() =>
            _bike.GetDetails();
    }

    public class SecurityPackage : BikeAccessories
    {
        public SecurityPackage(IBike bike):base(bike)
        {

        }

        public override string GetDetails() =>
            base.GetDetails() + " + Security Package";

        public override double GetPrice() =>
            base.GetPrice() + 1;
    }

    public class SportPackage : BikeAccessories
    {
        public SportPackage(IBike bike) : base(bike)
        {

        }

        public override string GetDetails() =>
            base.GetDetails() + " + Sport Package";

        public override double GetPrice() =>
            base.GetPrice() + 10;
    }

    public class BikeShop
    {
        public static void UpgradeBike()
        {
            var basicBike = new AluminiumBike();
            BikeAccessories upgraded = new SportPackage(basicBike);
            upgraded = new SecurityPackage(upgraded);

            Console.WriteLine($"Bike: '{upgraded.GetDetails()}' Cost: {upgraded.GetPrice()}");

        }
    }
}

Wyjście:

Bike: 'Aluminium Bike + Sport Package + Security Package' Cost: 111

Zobacz też

Bibliografia

Zewnętrzne linki