Design Patterns - Eine Kurzanleitung zum Decorator-Pattern.

Das Decorator-Muster ist ein Strukturmuster, mit dem Sie einem Objekt zusätzliche Funktionen dynamisch zuordnen können. Mit anderen Worten, der Kunde hat die Freiheit, ein Objekt zu erstellen und es dann zu erweitern, indem er ihm eine Vielzahl von „Funktionen“ hinzufügt. Eine gute Analogie zur Vereinfachung dieses Musters lautet: „Ein Geschenk einwickeln, in eine Schachtel legen und die Schachtel einwickeln“.

Das Decorator-Muster wird in strukturelle Entwurfsmuster eingeteilt, bei denen es ausschließlich um die Zusammensetzung von Klassen und Objekten geht. Strukturelle Klassenerstellungsmuster verwenden die Vererbung, um Schnittstellen zu erstellen. Strukturelle Objektmuster definieren Möglichkeiten zum Zusammensetzen von Objekten, um neue Funktionen zu erhalten. [von Design Patterns einfach erklärt]

Ein Rucksack im perfekten Beispiel eines Decorator-Musters:

  • Heutzutage können die Rucksäcke eine Vielzahl von Eigenschaften aufweisen. Die Funktionen können so einfach wie ein Laptop-Steckplatz, Seitentaschen oder sogar eine Power-Bank zum Aufladen über USB sein.
  • Das Hauptziel des Decorator-Musters besteht darin, dem Client zu ermöglichen, die erforderlichen Funktionen dynamisch, sicher und auf einfachste Weise hinzuzufügen. Es sollte so geradlinig und aufgeräumt sein wie der Rucksack auf dem Bild.
  • Der folgende Codeausschnitt wurde absichtlich geschrieben, da er so gut mit dem oben erwähnten Zitat übereinstimmt: „Ein Geschenk einpacken, in eine Schachtel packen und die Schachtel einpacken“. Wir bauen einen Rucksack zusammen, zu dem wir ein USB-Ladegerät hinzufügen, zu dem wir einen Laptop-Steckplatz hinzufügen, zu dem wir hinzufügen ...
int main // der Client
{
  IBackpack * bp = neuer LaptopSlot (neuer UsbCharge (...)));
  return 1;
}

Schritt 1 - Schlüsselwörter

Die Definition von Schlüsselwörtern ist das Geheimrezept in dieser Reihe von Kurzanleitungen. Diese Methode hat mir geholfen, die Entwurfsmuster wirklich zu verstehen, sie in meinem Kopf fest zu codieren und die Unterschiede zwischen anderen Entwurfsmustern zu verstehen.

  • Flexibilität: Wir möchten dem Kunden die Kraft und Flexibilität geben, einem Bauteil / Objekt dynamisch Merkmale hinzuzufügen, die als wertvoll angesehen werden könnten.
  • Funktionalität erweitern: Der Titel kann etwas irreführend sein. Bei diesem Muster geht es nicht nur darum, ein bestimmtes Objekt zu „dekorieren“, sondern hauptsächlich darum, seine Funktionalität zu erweitern.

Schritt 2 - Diagramme anhand eines Beispiels

Beginnen wir mit der Erläuterung des Diagramms von unten nach oben. Das Ziel ist es, einen Rucksack zu „montieren“ und ihm mehrere Funktionen hinzuzufügen.

  • Konkrete Dekorateure: Wir haben drei Beispiele für diese Klassen, die jeweils für die Erweiterung einer zusätzlichen Funktionalität verantwortlich sind: (1) LaptopSlot, (2) USBCharge, (3) WaterBottle. Sie übernehmen die Implementierung der Funktionalität und können einen Rucksack entsprechend „zusammenstellen“. Beachten Sie, dass sie einen Konstruktor haben, der einen Dekorator als Parameter erhält.
  • Decorator: Diese Klasse leitet die oben genannten Klassen ab und erbt von der Komponente IBackpack. Der Dekorateur hat auch eine Instanz von IBackpack. Nachdem IBackpack instanziiert wurde, wird es innerhalb der Methode assemble () verwendet.
  • Konkrete Komponente: Diese Klasse ist das wichtigste Puzzleteil, da sie den Schlüssel zum Verbinden aller Elemente darstellt. Es ist das Bauteil (Rucksack), das in der einfachsten Form vorliegt, die es bekommen kann. Beispielsweise besteht ein normaler Rucksack nur aus den „Schultergurten und dem Hauptfach“. Der schlichte Rucksack fungiert als Start einer Kette. Es wird an den Dekorateur-Konstruktor übergeben und selbst an den Konstruktor eines Betondekorateurs (um bestimmte Funktionen hinzuzufügen, d. H. Einen Laptop-Steckplatz), der an einen anderen Konstruktor eines Betondekorateurs übergeben werden kann, und so weiter.
  • Komponente: Dies ist eine abstrakte Ansicht des Objekts, das wir dekorieren möchten. Wir können verschiedene konkrete Komponenten erben lassen. In diesem Beispiel könnten wir das "PlainBackpack" in "OfficeBackpack", "CampingBackpack", "HikingBackpack" unterteilen, aber wir möchten es einfach halten.

Schritt 23 - Code durch Beispiel

Ich würde vorschlagen, den Code Klasse für Klasse aus meinem Git-Repository „Andreas Poyias“ oder den folgenden Ausschnitten (in der angegebenen Reihenfolge) zu kopieren und in einen der verfügbaren Online-C ++ - Editoren wie c ++ shell, jdoodle, onlineGDB einzufügen und auszuführen die Ausgabe zu beobachten. Dann lies die Kommentare oder die Beschreibung unten. Nehmen Sie sich die Zeit, es gründlich zu lesen (das bedeutet eine Minute, nicht weniger und nicht mehr).

IBackpack:
Der folgende Codeausschnitt ist eine einfache Vererbung, IBackpackist von PlainBackpackclass geerbt. Alle abgeleiteten Klassen müssen assemble () implementieren, da es sich um eine rein virtuelle Funktion handelt = 0 ;. Ein normaler Rucksack hat nur Schultergurte und das Hauptfach.

#include 
using namespace std;

Klasse IBackpack
{
Öffentlichkeit:
  Virtual Void Assemble () = 0;
  virtual ~ IBackpack () {}
};

class PlainBackpack: öffentliches IBackpack
{
Öffentlichkeit:
  virtual void assemble () {cout << "\ n Schultergurte und Hauptfach";}
};

RucksackDekorator:
Das obige Snippet ist für die Konstruktion eines einfachen Rucksacks verantwortlich. Jetzt dekorieren wir es mit dem BackpackDecorator. Der Dekorator erbt von dem IBackpack, was bedeutet, dass er die Methode assembl () implementieren muss. Es enthält auch ein IBackpack-Objekt, mit dem die Implementierung der assemble () -Methode abhängig vom Typ von m_decorator delegiert wird.

class BackpackDecorator: öffentliches IBackpack
{
Öffentlichkeit:
  BackpackDecorator (IBackpack * Dekorateur): m_Decorator (Dekorateur) {}
  
  virtuelle Leere montieren ()
  {
    m_Decorator-> assemble ();
  }
Privatgelände:
  IBackpack * m_Decorator;
};

Betondekorateure:
Das folgende Snippet zeigt drei verschiedene Dekoratoren, die von der BackpackDecorator-Klasse abgeleitet sind, die selbst von IBackpack erbt. In diesem Beispiel sind sie bis auf die Implementierung von assemble () alle identisch. Der erste fügt einen LaptopSlot hinzu, der zweite eine UBCharge und der dritte eine WaterBottle.

Klasse WithLaptopSlot: public BackpackDecorator
{
Öffentlichkeit:
  WithLaptopSlot (IBackpack * dcrator): BackpackDecorator (dcrator) {}
  virtuelle Leere montieren ()
  {
    BackpackDecorator :: assemble ();
    cout << "+ LaptopSlot";
  }
};
 
class WithUSBCharge: public BackpackDecorator
{
Öffentlichkeit:
    WithUSBCharge (IBackpack * dcrator): BackpackDecorator (dcrator) {}
    virtuelle Leere montieren ()
    {
        BackpackDecorator :: assemble ();
        cout << "+ USBCharge";
    }
};
 
Klasse WithWaterBottle: öffentlicher BackpackDecorator
{
Öffentlichkeit:
    WithWaterBottle (IBackpack * dcrator): BackpackDecorator (dcrator) {}
    virtuelle Leere montieren ()
    {
        BackpackDecorator :: assemble ();
        cout << "+ WaterBottle";
    }
};

Haupt (Kunde):
Die Hauptmethode wird als Client ausgeführt (dieselbe wie in den vorherigen Handbüchern). Wir sind endlich in der Lage, alle Teile des Puzzles zusammenzusetzen. Aber denken wir vorher daran, was im ersten Absatz des Blogs "Ein Geschenk einpacken, in eine Schachtel legen und die Schachtel einpacken" erwähnt wurde. Um dies zu verstehen, müssen wir den Aufbau des Rucksacks (im folgenden Ausschnitt) in umgekehrter Reihenfolge lesen:

  1. Erstellen Sie ein PlainBackpack.
  2. Gib es dem BackpackDecorator.
  3. Welches es weitergibt, um mit einem Laptopschlitz verziert zu werden.
  4. Im Gegenzug wird es mit einer USB-Gebühr verziert.
  5. Schließlich wird die "Box" mit einer Wasserflasche "umwickelt".

Es ist auch wichtig, die Druckreihenfolge beim Aufruf von assemble () zu beobachten. Die Reihenfolge hängt davon ab, wie die Konstruktoren initialisiert wurden. Es geht wieder vom einfachen Rucksack aus und ist bis zur Wasserflasche geschmückt.

int main ()
{
  IBackpack * pBackpack =
   neue WithWaterBottle (// 5
    neues WithUSBCharge (// 4
     neuer WithLaptopSlot (// 3
      neuer BackpackDecorator (// 2
       neues PlainBackpack ()))); // 1

  pBackpack-> assemble ();
  pBackpack löschen;

  return 0;
}
// Ausgabe
// Schultergurte und Hauptfach + LaptopSlot + USBCharge
// + Wasserflasche

Die Einfachheit, die dem Kunden geboten wird, ist einer der größten Vorteile.

  • Der Kunde hat die Möglichkeit, einen Rucksack mit allen verfügbaren Funktionen dynamisch zusammenzustellen.
  • Es ist einfach, leicht und sicher.
  • Der Client muss sich nicht mit dem Code mischen.
  • Das Hinzufügen eines zusätzlichen "abgeleiteten" Dekorators ist einfach und unabhängig von anderen abgeleiteten Dekoratoren.

Vergiss nicht, meinen Blog-Post zu mögen / zu klatschen und meinem Account zu folgen. Dies soll mir die Befriedigung geben, dass ich einigen Mitentwicklern geholfen habe und mich dazu drängen, weiter zu schreiben. Wenn es ein bestimmtes Designmuster gibt, über das Sie gerne mehr erfahren möchten, lassen Sie es mich in den Kommentaren unten wissen, damit ich es Ihnen in den nächsten Wochen zur Verfügung stellen kann.

Weitere Kurzanleitungen zu Entwurfsmustern:

  1. Design Patterns - Eine Kurzanleitung für Abstract Factory.
  2. Design Patterns - Eine Kurzanleitung für Bridge Pattern.
  3. Entwurfsmuster - Eine Kurzanleitung zum Builder-Muster.
  4. Design Patterns - Eine Kurzanleitung für Decorator Patterns.
  5. Entwurfsmuster - Eine Kurzanleitung für Fassadenmuster.
  6. Design Patterns - Eine Kurzanleitung für Observer Patterns.
  7. Entwurfsmuster - Eine Kurzanleitung für Singleton-Muster.