Verstehen Sie das „Dekorationsmuster“ anhand eines Beispiels aus der Praxis


167

Ich habe das Dekorationsmuster studiert, wie es in GOF dokumentiert ist .

Bitte helfen Sie mir, das Dekorationsmuster zu verstehen . Könnte jemand ein Anwendungsbeispiel geben, wo dies in der realen Welt nützlich ist?


8
Sie können hier einige Beispiele aus der Praxis in der Java-API finden: stackoverflow.com/questions/1673841/…
BalusC

Ein Artikel, der die Vorteile des Dekorationsmusters anhand
nbilal

Antworten:


226

Das Dekorationsmuster erreicht ein einziges Ziel, jedem Objekt dynamisch Verantwortlichkeiten hinzuzufügen.

Stellen Sie sich einen Pizzaladen vor. In der Pizzeria werden sie nur wenige Pizzasorten verkaufen und sie werden auch Beläge auf der Speisekarte anbieten. Stellen Sie sich nun eine Situation vor, in der die Pizzeria Preise für jede Kombination aus Pizza und Belag angeben muss. Selbst wenn es vier einfache Pizzen und acht verschiedene Beläge gibt, würde die Anwendung verrückt werden, wenn all diese konkreten Kombinationen von Pizzen und Belägen beibehalten würden.

Hier kommt das Dekorationsmuster.

Gemäß dem Dekorationsmuster implementieren Sie Beläge, da Dekorateure und Pizzen von den Dekorateuren dieser Beläge dekoriert werden. Praktisch jeder Kunde möchte Beläge nach seinem Wunsch und der endgültige Rechnungsbetrag setzt sich aus den Basispizzas und zusätzlich bestellten Belägen zusammen. Jeder Dekorateur würde über die Pizzen, die er dekoriert, und den Preis Bescheid wissen. Die GetPrice () -Methode des Topping-Objekts gibt den kumulierten Preis sowohl für die Pizza als auch für das Topping zurück.

BEARBEITEN

Hier ist ein Codebeispiel zur Erklärung oben.

public abstract class BasePizza
{
    protected double myPrice;

    public virtual double GetPrice()
    {
        return this.myPrice;
    }
}

public abstract class ToppingsDecorator : BasePizza
{
    protected BasePizza pizza;
    public ToppingsDecorator(BasePizza pizzaToDecorate)
    {
        this.pizza = pizzaToDecorate;
    }

    public override double GetPrice()
    {
        return (this.pizza.GetPrice() + this.myPrice);
    }
}

class Program
{
    [STAThread]
    static void Main()
    {
        //Client-code
        Margherita pizza = new Margherita();
        Console.WriteLine("Plain Margherita: " + pizza.GetPrice().ToString());

        ExtraCheeseTopping moreCheese = new ExtraCheeseTopping(pizza);
        ExtraCheeseTopping someMoreCheese = new ExtraCheeseTopping(moreCheese);
        Console.WriteLine("Plain Margherita with double extra cheese: " + someMoreCheese.GetPrice().ToString());

        MushroomTopping moreMushroom = new MushroomTopping(someMoreCheese);
        Console.WriteLine("Plain Margherita with double extra cheese with mushroom: " + moreMushroom.GetPrice().ToString());

        JalapenoTopping moreJalapeno = new JalapenoTopping(moreMushroom);
        Console.WriteLine("Plain Margherita with double extra cheese with mushroom with Jalapeno: " + moreJalapeno.GetPrice().ToString());

        Console.ReadLine();
    }
}

public class Margherita : BasePizza
{
    public Margherita()
    {
        this.myPrice = 6.99;
    }
}

public class Gourmet : BasePizza
{
    public Gourmet()
    {
        this.myPrice = 7.49;
    }
}

public class ExtraCheeseTopping : ToppingsDecorator
{
    public ExtraCheeseTopping(BasePizza pizzaToDecorate)
        : base(pizzaToDecorate)
    {
        this.myPrice = 0.99;
    }
}

public class MushroomTopping : ToppingsDecorator
{
    public MushroomTopping(BasePizza pizzaToDecorate)
        : base(pizzaToDecorate)
    {
        this.myPrice = 1.49;
    }
}

public class JalapenoTopping : ToppingsDecorator
{
    public JalapenoTopping(BasePizza pizzaToDecorate)
        : base(pizzaToDecorate)
    {
        this.myPrice = 1.49;
    }
}

104
Mag dieses Muster nicht ein bisschen. Vielleicht ist es das Beispiel. Das Hauptproblem, das ich in Bezug auf OOD habe, ist, dass ein Belag keine Pizza ist . Das Topping nach dem Preis der Pizza zu fragen, auf die es angewendet wird, passt einfach nicht zu mir. Es ist jedoch ein sehr nachdenkliches und detailliertes Beispiel, deshalb möchte ich Sie nicht dafür anklopfen.
Tom W

39
@ TomW Ich denke, ein Teil des Problems ist die Benennung. Alle "Topping" -Klassen sollten "PizzaWith <Topping>" heißen. Zum Beispiel "PizzaWithMushrooms".
Josh Noe

2
Meiner Meinung nach werden Dekorateure am besten so flach wie möglich eingesetzt. Damit meine ich so wenig "Dekorateure, die Dekorateure einwickeln" wie möglich. Vielleicht ist dieses Beispiel nicht das passendste. Aber es ist ziemlich gründlich, was schön ist.
thekingoftruth

17
Aus einer anderen Perspektive kommt dies nicht einmal der "realen Welt" nahe. In der realen Welt sollten Sie nicht jedes Mal neu kompilieren, wenn Sie einen neuen Belag im Menü hinzufügen (oder den Preis ändern müssen). Toppings werden (normalerweise) in einer Datenbank gespeichert und machen das obige Beispiel somit unbrauchbar.
Stelios Adamantidis

4
^ Dies. Ich denke, das hat mich die ganze Zeit gestört, als ich dieses Muster studiert habe. Wenn ich eine Softwarefirma wäre und Pizza-Shop-Software schreiben würde, müsste ich nicht jedes Mal neu kompilieren und neu versenden müssen. Ich möchte eine Zeile in eine Tabelle im Backend einfügen oder etwas, das sich leicht um ihre Anforderungen kümmert. Gut gesagt, @Stelios Adamantidis. Ich denke, die größte Stärke des Musters wäre es dann, Klassen von Drittanbietern zu modifizieren.
Canucklesandwich

33

Dies ist ein einfaches Beispiel für das dynamische Hinzufügen eines neuen Verhaltens zu einem vorhandenen Objekt oder des Decorator-Musters. Aufgrund der Natur dynamischer Sprachen wie Javascript wird dieses Muster Teil der Sprache selbst.

// Person object that we will be decorating with logging capability
var person = {
  name: "Foo",
  city: "Bar"
};

// Function that serves as a decorator and dynamically adds the log method to a given object
function MakeLoggable(object) {
  object.log = function(property) {
    console.log(this[property]);
  }
}

// Person is given the dynamic responsibility here
MakeLoggable(person);

// Using the newly added functionality
person.log('name');


Einfach und präzise! Tolles Beispiel!
Nagendra547

1
Ich denke nicht, dass das Konzept des Dekorationsmusters hier anwendbar ist. Eigentlich ist es überhaupt kein Muster!. Ja, Sie fügen zur Laufzeit eine neue Methode hinzu. Und wahrscheinlich können Sie innerhalb eines switchoder eines einfachen ifModells behaupten, dass dies ein großartiges Beispiel für das dynamische Hinzufügen von Verhalten zu einer Klasse ist. ABER wir benötigen mindestens zwei Klassen, um einen Dekorateur und dekorierte Objekte in diesem Muster zu definieren.
Iman

1
@Zich Ich verstehe, dass es in meinem Beispiel keinen Dekorateur gibt, aber das lässt sich leicht beheben, indem eine Funktion hinzugefügt wird, die als Dekorateur dient. Aber in meinem Beispiel gibt es ein dekoriertes Objekt. Sagt das Muster irgendwo, dass Sie speziell zwei Klassen benötigen ?
Anurag

18

Es ist erwähnenswert, dass das Java-E / A-Modell auf dem Dekorationsmuster basiert. Die Überlagerung dieses Lesers mit diesem Leser über ... ist ein wirklich reales Beispiel für Dekorateur.


Gibt es andere Beispiele in echten öffentlichen APIs? Dies ist der einzige, den ich kenne.
Josiah Yoder

Es scheint, dass in allen Wrapper-Funktionen in der Natur eine Art Dekorationsmuster eingebaut ist. Ist es das, was ich denke?
Harvey Lin

Gutes Beispiel !!
Nagendra547

8

Beispiel - Szenario - Angenommen, Sie schreiben ein Verschlüsselungsmodul. Diese Verschlüsselung kann die gelöschte Datei mit DES - Data Encryption Standard verschlüsseln. Ebenso können Sie in einem System die Verschlüsselung als AES - Advance-Verschlüsselungsstandard verwenden. Sie können auch die Kombination von Verschlüsselung verwenden - zuerst DES, dann AES. Oder Sie können zuerst AES, dann DES haben.

Diskussion - Wie werden Sie mit dieser Situation umgehen? Sie können das Objekt solcher Kombinationen nicht weiter erstellen - zum Beispiel - AES und DES - insgesamt 4 Kombinationen. Sie benötigen also 4 einzelne Objekte. Dies wird komplex, wenn der Verschlüsselungstyp zunimmt.

Lösung - Bauen Sie die Stapelkombinationen je nach Bedarf zur Laufzeit weiter auf. Ein weiterer Vorteil dieses Stapelansatzes besteht darin, dass Sie ihn leicht abwickeln können.

Hier ist die Lösung - in C ++.

Erstens benötigen Sie eine Basisklasse - eine grundlegende Einheit des Stapels. Sie können als Basis des Stapels denken. In diesem Beispiel handelt es sich um eine klare Datei. Folgen wir immer dem Polymorphismus. Machen Sie zuerst eine Schnittstellenklasse dieser Grundeinheit. Auf diese Weise können Sie es nach Ihren Wünschen implementieren. Außerdem müssen Sie nicht an die Abhängigkeit denken, während Sie diese grundlegende Einheit einbeziehen.

Hier ist die Schnittstellenklasse -

class IclearData
{
public:

    virtual std::string getData() = 0;
    virtual ~IclearData() = 0;
};

IclearData::~IclearData()
{
    std::cout<<"Destructor called of IclearData"<<std::endl;
}

Implementieren Sie nun diese Schnittstellenklasse -

class clearData:public IclearData
{
private:

    std::string m_data;

    clearData();

    void setData(std::string data)
        {
            m_data = data;
        }

public:

    std::string getData()
    {
        return m_data;
    }

    clearData(std::string data)
    {
        setData(data);
    }

    ~clearData()
    {
        std::cout<<"Destructor of clear Data Invoked"<<std::endl;
    }

};

Lassen Sie uns nun eine abstrakte Klasse für Dekorateure erstellen, die erweitert werden kann, um jede Art von Geschmacksrichtungen zu erstellen. Hier ist die Geschmacksrichtung der Verschlüsselungstyp. Diese abstrakte Klasse des Dekorateurs ist mit der Basisklasse verwandt. Somit ist der Dekorator eine Art Schnittstellenklasse. Daher müssen Sie die Vererbung verwenden.

class encryptionDecorator: public IclearData
{

protected:
    IclearData *p_mclearData;

    encryptionDecorator()
    {
      std::cout<<"Encryption Decorator Abstract class called"<<std::endl;
    }

public:

    std::string getData()
    {
        return p_mclearData->getData();
    }

    encryptionDecorator(IclearData *clearData)
    {
        p_mclearData = clearData;
    }

    virtual std::string showDecryptedData() = 0;

    virtual ~encryptionDecorator() = 0;

};

encryptionDecorator::~encryptionDecorator()
{
    std::cout<<"Encryption Decorator Destructor called"<<std::endl;
}

Lassen Sie uns nun eine konkrete Dekorationsklasse erstellen - Verschlüsselungstyp - AES -

const std::string aesEncrypt = "AES Encrypted ";

class aes: public encryptionDecorator
{

private:

    std::string m_aesData;

    aes();

public:

    aes(IclearData *pClearData): m_aesData(aesEncrypt)
    {
        p_mclearData = pClearData;
        m_aesData.append(p_mclearData->getData());
    }

    std::string getData()
        {
            return m_aesData;
        }

    std::string showDecryptedData(void)
    {
        m_aesData.erase(0,m_aesData.length());
        return m_aesData;
    }

};

Angenommen, der Dekoratortyp ist DES -

const std :: string desEncrypt = "DES Encrypted";

class des: public encryptionDecorator
{

private:

    std::string m_desData;

    des();

public:

    des(IclearData *pClearData): m_desData(desEncrypt)
    {
        p_mclearData = pClearData;
        m_desData.append(p_mclearData->getData());
    }

    std::string getData(void)
        {
            return m_desData;
        }

    std::string showDecryptedData(void)
    {
        m_desData.erase(0,desEncrypt.length());
        return m_desData;
    }

};

Lassen Sie uns einen Client-Code erstellen, um diese Decorator-Klasse zu verwenden -

int main()
{
    IclearData *pData = new clearData("HELLO_CLEAR_DATA");

    std::cout<<pData->getData()<<std::endl;


    encryptionDecorator *pAesData = new aes(pData);

    std::cout<<pAesData->getData()<<std::endl;

    encryptionDecorator *pDesData = new des(pAesData);

    std::cout<<pDesData->getData()<<std::endl;

    /** unwind the decorator stack ***/
    std::cout<<pDesData->showDecryptedData()<<std::endl;

    delete pDesData;
    delete pAesData;
    delete pData;

    return 0;
}

Sie sehen die folgenden Ergebnisse -

HELLO_CLEAR_DATA
Encryption Decorator Abstract class called
AES Encrypted HELLO_CLEAR_DATA
Encryption Decorator Abstract class called
DES Encrypted AES Encrypted HELLO_CLEAR_DATA
AES Encrypted HELLO_CLEAR_DATA
Encryption Decorator Destructor called
Destructor called of IclearData
Encryption Decorator Destructor called
Destructor called of IclearData
Destructor of clear Data Invoked
Destructor called of IclearData

Hier ist das UML-Diagramm - Klassendarstellung davon. Falls Sie den Code überspringen und sich auf den Designaspekt konzentrieren möchten.

Geben Sie hier die Bildbeschreibung ein


1
Ist das Beispiel nicht besser geeignet strategy pattern?
Exexzian

@exexzian Ja, meine Schüler schlagen mir immer wieder eine Liste von Strategien für diese Art von Problem vor, und es scheint mir auch die sauberste Lösung zu sein.
Josiah Yoder

Nein, mit dem Strategiemuster können Sie die Verschlüsselungsmethoden nicht kombinieren. Bisher müssten Sie für jede mögliche Kombination eine Strategieklasse erstellen.
Deetz

4

Mit dem Dekorationsmuster können Sie eine Funktionalität Ihres Objekts ändern oder konfigurieren, indem Sie sie mit anderen ähnlichen Unterklassen dieses Objekts verketten.

Das beste Beispiel wären die Klassen InputStream und OutputStream im Paket java.io.

    File file=new File("target","test.txt");
    FileOutputStream fos=new FileOutputStream(file);
    BufferedOutputStream bos=new BufferedOutputStream(fos);
    ObjectOutputStream oos=new ObjectOutputStream(bos);


    oos.write(5);
    oos.writeBoolean(true);
    oos.writeBytes("decorator pattern was here.");


//... then close the streams of course.

In diesem Fall beginnt die aufrufende Kette bei ObjectOutputStream, geht dann bis zur File-Klasse, dann gibt die File-Klasse den Wert zurück, dann addieren die anderen drei Unterklassen sie alle und schließlich gibt der Wert der ObjectOutputStream-Methode ihn zurück das richtig?
Harvey Lin

3

Was ist Decorator Design Pattern in Java?

Die formale Definition des Decorator-Musters aus dem GoF-Buch (Design Patterns: Elements of Reusable Object-Oriented Software, 1995, Pearson Education, Inc., Veröffentlichung als Pearson Addison Wesley) besagt, dass Sie Folgendes können:

"Fügen Sie einem Objekt dynamisch zusätzliche Verantwortlichkeiten hinzu. Dekorateure bieten eine flexible Alternative zur Unterklasse, um die Funktionalität zu erweitern."

Nehmen wir an, wir haben eine Pizza und möchten sie mit Belägen wie Hühnchen-Masala, Zwiebel und Mozzarella-Käse dekorieren. Mal sehen, wie man es in Java implementiert ...

Programm zur Demonstration der Implementierung von Decorator Design Pattern in Java.

Pizza.java:

<!-- language-all: lang-html -->

package com.hubberspot.designpattern.structural.decorator;

public class Pizza {

public Pizza() {

}

public String description(){
    return "Pizza";
}

}



package com.hubberspot.designpattern.structural.decorator;

public abstract class PizzaToppings extends Pizza {

public abstract String description();

}

package com.hubberspot.designpattern.structural.decorator;

public class ChickenMasala extends PizzaToppings {

private Pizza pizza;

public ChickenMasala(Pizza pizza) {
    this.pizza = pizza;
}

@Override
public String description() {
    return pizza.description() + " with chicken masala, ";
}

}



package com.hubberspot.designpattern.structural.decorator;

public class MozzarellaCheese extends PizzaToppings {

private Pizza pizza;

public MozzarellaCheese(Pizza pizza) {
    this.pizza = pizza;
}

@Override
public String description() {
    return pizza.description() + "and mozzarella cheese.";
}
}



package com.hubberspot.designpattern.structural.decorator;

public class Onion extends PizzaToppings {

private Pizza pizza;

public Onion(Pizza pizza) {
    this.pizza = pizza;
}

@Override
public String description() {
    return pizza.description() + "onions, ";
}

}



package com.hubberspot.designpattern.structural.decorator;

public class TestDecorator {

public static void main(String[] args) {

    Pizza pizza = new Pizza();

    pizza = new ChickenMasala(pizza);
    pizza = new Onion(pizza);
    pizza = new MozzarellaCheese(pizza);

    System.out.println("You're getting " + pizza.description());

}

}

3

Ich habe bei meiner Arbeit ausgiebig Decorator-Muster verwendet. Ich habe in meinem Blog einen Beitrag darüber verfasst, wie man ihn mit der Protokollierung verwendet.


Ich mag es nicht, dass du nur einen Link als Antwort geworfen hast. Aber dein Blog-Artikel ist so hilfreich, dass ich nur upvoten musste :). Jetzt verstehe ich es wirklich. Jeder kommt mit Pizza und Sie mit einem perfekten Beispiel.
Niklas Raab

2

Mit dem Dekorationsmuster können Sie Objekten dynamisch Verhalten hinzufügen.

Nehmen wir ein Beispiel, in dem Sie eine App erstellen müssen, die den Preis für verschiedene Arten von Burgern berechnet. Sie müssen mit verschiedenen Burgervarianten umgehen, z. B. "groß" oder "mit Käse", von denen jeder einen Preis im Verhältnis zum Basisburger hat. ZB 10 USD für Burger mit Käse, 15 USD für großen Burger usw. hinzufügen

In diesem Fall könnten Sie versucht sein, Unterklassen zu erstellen, um diese zu behandeln. Wir könnten dies in Ruby ausdrücken als:

class Burger
  def price
    50
  end
end

class BurgerWithCheese < Burger
  def price
    super + 15
  end
end

Im obigen Beispiel erbt die BurgerWithCheese-Klasse von Burger und überschreibt die Preismethode, um dem in der Superklasse definierten Preis 15 USD hinzuzufügen. Sie würden auch eine LargeBurger-Klasse erstellen und den Preis relativ zu Burger definieren. Sie müssen aber auch eine neue Klasse für die Kombination von "groß" und "mit Käse" definieren.

Was passiert nun, wenn wir "Burger mit Pommes" servieren müssen? Wir haben bereits 4 Klassen, um diese Kombinationen zu handhaben, und wir müssen 4 weitere hinzufügen, um alle Kombinationen der 3 Eigenschaften zu handhaben - "groß", "mit Käse" und "mit Pommes". Wir brauchen jetzt 8 Klassen. Fügen Sie eine weitere Eigenschaft hinzu und wir benötigen 16. Dies wird als 2 ^ n wachsen.

Versuchen wir stattdessen, einen BurgerDecorator zu definieren, der ein Burger-Objekt aufnimmt:

class BurgerDecorator
  def initialize(burger)
    self.burger = burger
  end
end

class BurgerWithCheese < BurgerDecorator
  def price
    self.burger.price + 15
  end
end

burger = Burger.new
cheese_burger = BurgerWithCheese.new(burger)
cheese_burger.price   # => 65

Im obigen Beispiel haben wir eine BurgerDecorator-Klasse erstellt, von der die BurgerWithCheese-Klasse erbt. Wir können die "große" Variante auch darstellen, indem wir die LargeBurger-Klasse erstellen. Jetzt könnten wir einen großen Burger mit Käse zur Laufzeit definieren als:

b = LargeBurger.new(cheese_burger)
b.price  # => 50 + 15 + 20 = 85

Erinnern Sie sich, wie die Verwendung der Vererbung zum Hinzufügen der Variante "mit Pommes" das Hinzufügen von 4 weiteren Unterklassen beinhalten würde? Mit Dekorateuren würden wir nur eine neue Klasse erstellen, BurgerWithFries, um die neue Variante zu handhaben und dies zur Laufzeit zu handhaben. Jede neue Eigenschaft würde nur mehr Dekorateur benötigen, um alle Permutationen abzudecken.

PS. Dies ist die Kurzversion eines Artikels, den ich über die Verwendung des Dekorationsmusters in Ruby geschrieben habe . Sie können ihn lesen, wenn Sie detailliertere Beispiele finden möchten.


2

Dekorateur:

  1. Fügen Sie dem Objekt zur Laufzeit Verhalten hinzu . Vererbung ist der Schlüssel, um diese Funktionalität zu erreichen, was sowohl Vor- als auch Nachteil dieses Musters ist.
  2. Es verbessert das Verhalten der Schnittstelle.
  3. Decorator kann als entartetes Composite mit nur einer Komponente angesehen werden. Ein Decorator fügt jedoch zusätzliche Verantwortlichkeiten hinzu - er ist nicht für die Objektaggregation vorgesehen.
  4. Die Decorator-Klasse deklariert eine Zusammensetzungsbeziehung zur LCD-Schnittstelle (Lowest Class Denominator), und dieses Datenelement wird in seinem Konstruktor initialisiert.
  5. Mit Decorator können Sie Objekten Verantwortlichkeiten hinzufügen, ohne sie unterzuordnen

Siehe Quellenherstellung Informationen finden Artikel .

Decorator (Abstract) : Dies ist eine abstrakte Klasse / Schnittstelle, die die Komponentenschnittstelle implementiert. Es enthält die Komponentenschnittstelle. In Abwesenheit dieser Klasse benötigen Sie viele Unterklassen von ConcreteDecorators für verschiedene Kombinationen. Die Zusammensetzung der Komponente reduziert unnötige Unterklassen.

JDK-Beispiel:

BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File("a.txt")));
while(bis.available()>0)
{
        char c = (char)bis.read();
        System.out.println("Char: "+c);;
}

Schauen Sie sich die folgende SE-Frage für UML-Diagramme und Codebeispiele an.

Dekorationsmuster für E / A.

Nützliche Artikel:

journaldev

Wikipedia

Echtes Wortbeispiel für ein Dekorationsmuster: VendingMachineDecorator wurde erklärt @

Wann sollte das Dekorationsmuster verwendet werden?

Beverage beverage = new SugarDecorator(new LemonDecorator(new Tea("Assam Tea")));
beverage.decorateBeverage();

beverage = new SugarDecorator(new LemonDecorator(new Coffee("Cappuccino")));
beverage.decorateBeverage();

Im obigen Beispiel wurde Tee oder Kaffee (Getränk) von Zucker und Zitrone dekoriert.


2

Das Dekorationsmuster erreicht ein einziges Ziel , jedem Objekt dynamisch Verantwortlichkeiten hinzuzufügen .

Das Java-E / A-Modell basiert auf einem Dekorationsmuster.

Java IO als Dekorationsmuster


1

Auf Wikipedia gibt es ein Beispiel für die Dekoration eines Fensters mit Bildlaufleiste:

http://en.wikipedia.org/wiki/Decorator_pattern

Hier ist ein weiteres sehr "reales" Beispiel für "Teammitglied, Teamleiter und Manager", das zeigt, dass das Dekorationsmuster durch einfache Vererbung unersetzbar ist:

https://zishanbilal.wordpress.com/2011/04/28/design-patterns-by-examples-decorator-pattern/


Dieser Zishan Bilal Link ist großartig - das beste Beispiel, das ich gesehen habe
stonedauwg

1

Vor einiger Zeit hatte ich eine Codebasis in das Decorator-Muster umgestaltet, daher werde ich versuchen, den Anwendungsfall zu erklären.

Nehmen wir an, wir haben eine Reihe von Diensten und basierend darauf, ob der Benutzer die Lizenz für einen bestimmten Dienst erworben hat, müssen wir den Dienst starten.

Alle Dienste haben eine gemeinsame Schnittstelle

interface Service {
  String serviceId();
  void init() throws Exception;
  void start() throws Exception;
  void stop() throws Exception;
}

Pre Refactoring

abstract class ServiceSupport implements Service {
  public ServiceSupport(String serviceId, LicenseManager licenseManager) {
    // assign instance variables
  }

  @Override
  public void init() throws Exception {
    if (!licenseManager.isLicenseValid(serviceId)) {
       throw new Exception("License not valid for service");
    }
    // Service initialization logic
  }
}

Wenn Sie genau beobachten, ServiceSupportist abhängig von LicenseManager. Aber warum sollte es abhängig sein LicenseManager? Was wäre, wenn wir einen Hintergrunddienst benötigen, der die Lizenzinformationen nicht überprüfen muss? In der gegenwärtigen Situation müssen wir irgendwie trainieren, LicenseManagerum zurückzukehrentrue für Hintergrunddienste zurückzukehren. Dieser Ansatz schien mir nicht gut zu sein. Meiner Meinung nach waren Lizenzprüfung und andere Logik orthogonal zueinander.

Also kommt Decorator Pattern zur Rettung und hier beginnt das Refactoring mit TDD.

Nach dem Refactoring

class LicensedService implements Service {
  private Service service;
  public LicensedService(LicenseManager licenseManager, Service service) {
    this.service = service;
  }

  @Override
  public void init() {
    if (!licenseManager.isLicenseValid(service.serviceId())) {
      throw new Exception("License is invalid for service " + service.serviceId());
    }
    // Delegate init to decorated service
    service.init();
  }

  // override other methods according to requirement
}

// Not concerned with licensing any more :)
abstract class ServiceSupport implements Service {
  public ServiceSupport(String serviceId) {
    // assign variables
  }

  @Override
  public void init() {
    // Service initialization logic
  }
}

// The services which need license protection can be decorated with a Licensed service
Service aLicensedService = new LicensedService(new Service1("Service1"), licenseManager);
// Services which don't need license can be created without one and there is no need to pass license related information
Service aBackgroundService = new BackgroundService1("BG-1");

Imbissbuden

  • Der Zusammenhalt des Codes wurde besser
  • Unit-Tests wurden einfacher, da beim Testen von ServiceSupport keine Lizenzierung verspottet werden muss
  • Sie müssen die Lizenzierung nicht durch spezielle Überprüfungen für Hintergrunddienste umgehen
  • Richtige Aufgabenteilung

1

Nehmen wir ein Beispiel für PubG. Sturmgewehre funktionieren am besten mit 4-fachem Zoom und während wir darauf sind, würden wir auch Kompensator und Suppressor benötigen. Es reduziert den Rückstoß und reduziert das Schussgeräusch sowie das Echo. Wir müssen diese Funktion implementieren, damit die Spieler ihre Lieblingswaffe und ihr Zubehör kaufen können. Spieler können die Waffe oder einen Teil des Zubehörs oder das gesamte Zubehör kaufen und werden entsprechend belastet.

Mal sehen, wie das Dekorationsmuster hier angewendet wird:

Angenommen, jemand möchte SCAR-L mit allen drei oben genannten Zubehörteilen kaufen.

  1. Nimm ein Objekt von SCAR-L
  2. Dekorieren (oder fügen) Sie den SCAR-L mit 4x Zoomobjekt hinzu
  3. Dekorieren Sie den SCAR-L mit einem Suppressor-Objekt
  4. Dekorieren Sie den SCAR-L mit einem Kompressorobjekt
  5. Rufen Sie die Kostenmethode auf und lassen Sie jeden Objektdelegierten die Kosten mithilfe der Kostenmethode für Zubehör hinzufügen

Dies führt zu einem Klassendiagramm wie dem folgenden:

Dekorationsmuster bei der Arbeit

Jetzt können wir Klassen wie diese haben:

public abstract class Gun {     
    private Double cost;    
    public Double getCost() {           
        return cost;        
       }    
    }

public abstract class GunAccessories extends Gun {  }

public class Scarl extends Gun {    
    public Scarl() {            
        cost = 100;
        }   
     }

public class Suppressor extends GunAccessories {        
    Gun gun;        
    public Suppressor(Gun gun) {            
    cost = 5;           
    this.gun = gun;     
    }               
    public double getCost(){            
        return cost + gun.getCost();
    }
}

public class GunShop{   
    public static void main(String args[]){         
    Gun scarl = new Scarl();                
    scarl = new Supressor(scarl);
    System.out.println("Price is "+scarl.getCost());
    }      
}

In ähnlicher Weise können wir auch anderes Zubehör hinzufügen und unsere Waffe dekorieren.

Referenz:

https://nulpointerexception.com/2019/05/05/a-beginner-guide-to-decorator-pattern/


0

Decorator Design Pattern : Dieses Muster hilft, die Eigenschaften eines Objekts zur Laufzeit zu ändern. Es bietet einem Objekt verschiedene Geschmacksrichtungen und gibt Flexibilität bei der Auswahl der Zutaten, die wir für diese Geschmacksrichtung verwenden möchten.

Beispiel aus dem wirklichen Leben: Nehmen wir an, Sie haben einen Hauptkabinensitz in einem Flug. Jetzt können Sie mit dem Sitz mehrere Annehmlichkeiten auswählen. Jede Annehmlichkeit hat ihre eigenen Kosten. Wenn sich ein Benutzer für Wifi und Premium-Essen entscheidet, wird ihm Sitzplatz + Wifi + Premium-Essen in Rechnung gestellt.

Geben Sie hier die Bildbeschreibung ein

In diesem Fall kann uns das Dekorationsmuster wirklich helfen. Besuchen Sie den obigen Link, um mehr über das Dekorationsmuster und die Implementierung eines realen Beispiels zu erfahren.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.