Entwurfsmuster - Eine Kurzanleitung zum Builder-Muster.

Das Builder-Muster ist ein kreatives Entwurfsmuster, das die schrittweise (oder ziegelweise) Erstellung komplexer Objekte behandelt. Es ist wahrscheinlich das am einfachsten zu erkennende Muster, wenn der vorhandene Code ein solches Design benötigt. Das Builder-Muster ermöglicht die Erstellung verschiedener Darstellungen eines Objekts unter Verwendung desselben Konstruktionscodes.

Builder-Muster werden in die Erstellungsentwurfsmuster eingeteilt, bei denen es ausschließlich um die Instanziierung von Klassen und Objekten geht. Genauer gesagt, wie Vererbung (Klassenerstellungsmuster) oder Delegierung (Objekterstellungsmuster) effektiv genutzt werden können. [von Design Patterns einfach erklärt]

Beispiel: Beginnen wir mit einem Beispiel zu meinem Lieblingsthema. Essen!! Genauer gesagt handelt das Beispiel von Pizza. Die Pizza besteht aus drei Schichten, dem Teig, der Sauce und den Belägen. Fragen wir uns nun, wie wir ein Pizzaobjekt „konstruieren“, indem wir zwischen verschiedenen Belägen und verschiedenen Saucen auswählen.

Fragen vor dem Entwerfen einer Lösung für dieses Problem:

  • Wie würde der Pizza-Klassenkonstruktor aussehen?
  • Sollten wir mehrere Überladungskonstruktoren mit Parametern für alle Kombinationen wie Saucen, Toppings und Saucen usw. haben? Was ist, wenn wir auch verschiedene Arten von Teig haben?
  • Was ist, wenn wir später beschließen, Käse oder verschiedene Käsesorten hinzuzufügen? Wäre das einfach umzusetzen? Was ist mit Abhängigkeiten von vorhandenem Code?
  • Sollten wir nur einen Standardkonstruktor haben, der eine "einfache Pizza" erstellt? Würde das den Kunden nicht dazu zwingen, eine Reihe von Setzern entsprechend anzurufen? Ist das sicher? Was ist, wenn der Kunde vergisst und wir dem Kunden eine einfache Pizza schicken?

Die Lösung für all diese Fragen ist das Builder-Muster. Wir brauchen einen "Baumeister", der Schritt für Schritt eine Pizza baut. Gleichzeitig kann der Kunde sicher sein, eine bestimmte Pizza zu „bestellen“ und vom Erbauer für ihn bauen zu lassen.

Schritt 1 - Schlüsselwörter

  1. Darstellung: Ein Objekt kann unterschiedliche Darstellungen haben. Zum Beispiel kann eine Pizza Tomaten-Mozzarella-Belag haben und eine andere Darstellung der Pizza wäre mit Pilzen und Parmaschinken.
  2. Konstruktionscode: Objekte können auf verschiedene Arten konstruiert werden, zum Beispiel mit Konstruktoren. Konstruktoren können überladen werden, sie haben den gleichen Namen (Name der Klasse), aber eine andere Anzahl / Art von Argumenten. Abhängig von der Anzahl und Art der übergebenen Argumente wird der jeweilige Konstruktor aufgerufen.

Es ist leicht, in die Falle zu tappen, eine Klasse von zahlreichen Konstruktoren zu haben, bei denen jeder eine andere Anzahl von Parametern verwendet. Der Entwickler muss die Klasse mit der richtigen Parameterkombination für jede Situation instanziieren. Dieses Problem hat einen Namen, es ist ein beliebtes Antimuster, das als Teleskopkonstruktor bezeichnet wird, und das Buildermuster ist die Lösung für dieses Problem. Vereinfachen wir das Builder-Muster, indem wir direkt sagen:

Das Hauptziel des Builder-Musters besteht darin, die Anzahl der Überladungskonstruktoren so gering wie möglich zu halten, um die Konstruktion mehrerer Darstellungen eines Objekts zu unterstützen.

Schritt 2 - Diagramm (Beispiel)

Bleiben wir beim Pizza-Beispiel. Zusammenfassend haben wir die Pizzaklasse, den Koch und die Betonbauer, die vom abstrakten Erbauer erben. Wir werden das Diagramm von unten nach oben erklären:

  • Pizza_Product: Diese Klasse ist die eigentliche Pizza. Es wird durch drei Attribute dargestellt: (1) Teig, (2) Sauce und (3) Belag.
  • ConcreteBuilder: Jeder Betonbauer ist für eine bestimmte Darstellung verantwortlich. In diesem Beispiel haben wir Margherita und Spicy Pizza. Beide Betonbauer sind dafür verantwortlich, ihre eigene Darstellung der Pizza auf der Grundlage der drei oben aufgeführten Attribute zu erstellen.
  • AbstractBuilder: Enthält eine Pizza-Membervariable und die davon geerbten Betonbauer.
  • Cook_Director: In diesem Beispiel ist der Direktor der eigentliche Koch. Die Klasse ist dafür verantwortlich, die Konstruktion einer bestimmten Repräsentation zu initiieren, indem sie die Teile so zusammenstellt, dass der Erbauer den Bedürfnissen des Direktors folgt und sie anpasst.

Schritt 3 - 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).

Produkt:
Dies ist die Pizza-Klasse. Es ist eine einfache Klasse mit drei Setzern und einer taste () -Methode, die alle Zutaten druckt.

#include 
#include  // unique_ptr
using namespace std;
Klasse Pizza_Product
{
Öffentlichkeit:
 void setDough (const string & teig) {m_dough = teig; }
 void setSauce (const string & sauce) {m_sauce = sauce; }
 void setTopping (const string & topping) {m_topping = topping; }
void taste () const
{
  cout << "Pizza with" << m_dough << "teig"
       << m_sauce << "sauce and"
       << m_topping << "Richtfest. Mmmmmmm." << endl;
}
Privatgelände:
 string m_dough;
 string m_sauce;
 string m_topping;
};

Abstrakter Builder:
Der Abstract Builder ist eine Schnittstelle, die ein Pizza-Objekt enthält. Es verfügt über einen Getter, der das Pizza-Objekt zurückgibt, und eine Methode zum Instanziieren des Pizza-Objekts. Außerdem werden die drei Builder-Methoden angegeben, die von den Betonbauern weiter unten implementiert werden.

Klasse Pizza_Builder
{
Öffentlichkeit:
  virtual ~ Pizza_Builder () {};
  Pizza_Product * getPizza () {return m_pizza.release (); }
  void createNewPizzaProduct ()
  {
    m_pizza = make_unique  ();
  }
  virtueller void buildDough () = 0;
  virtuelle Leere buildSauce () = 0;
  virtuelle Leere buildTop () = 0;
geschützt:
  unique_ptr  m_pizza;
};

Betonbauer:
Zwei Beispiele für Betonbauer und zwei Darstellungen einer Pizza. Beide implementieren ihre eigenen Erstellungsmethoden mit dem Objekt m_pizza aus der übergeordneten Klasse (Builder für Zusammenfassung).

Klasse Margherita_ConcreteBuilder: public Pizza_Builder
{
Öffentlichkeit:
 virtuelle Leere buildDough () {m_pizza-> setDough ("cross"); }
 virtual void buildSauce () {m_pizza-> setSauce ("Tomate"); }
 virtuelle Leere buildTop () {m_pizza-> setTopping ("Mozzarela + Basilikum"); }
};
Klasse Spicy_ConcreteBuilder: public Pizza_Builder
{
Öffentlichkeit:
 virtual void buildDough () {m_pizza-> setDough ("Pfanne gebacken"); }
 virtual void buildSauce () {m_pizza-> setSauce ("Tomate + Chili"); }
 virtuelle Leere buildTop () {m_pizza-> setTopping ("Pepperoni + Salami"); }
};

Direktor:
Diese Klasse fasst alles zusammen. Es hat eine Pizza_Builder-Variable. Es verwendet makePizza (), um einen konkreten Builder als Parameter zu erhalten. Ruft dann die Build-Operationen auf, die für beide Darstellungen entsprechend funktionieren. Daher haben wir den Zweck, einen Konstruktionscode zu haben, um verschiedene Pizzasorten darzustellen. Mit der tastePizza () -Methode wird der Inhalt einer Pizza gedruckt.

Klasse Cook_Director
{
Öffentlichkeit:
 void tastePizza () {m_pizzaBuilder-> getPizza () -> taste (); }
void makePizza (Pizza_Builder * pb)
 {
   m_pizzaBuilder = pb;
   m_pizzaBuilder-> createNewPizzaProduct ();
   m_pizzaBuilder-> buildDough ();
   m_pizzaBuilder-> buildSauce ();
   m_pizzaBuilder-> buildTop ();
 }
Privatgelände:
 Pizza_Builder * m_pizzaBuilder;
};

Haupt (Client):
 Die Hauptmethode wird als Client ausgeführt (dieselbe wie in den vorherigen Handbüchern). Es ist für den Kunden so einfach, verschiedene Darstellungen einer Pizza zu erstellen. Wir brauchen den Regisseur und dann, wenn wir nur zwei verschiedene Betonbauer als Parameter an die Marke Pizza übergeben, können wir zwei verschiedene Pizzasorten probieren.

int main ()
{
  Cook_Director cook;
  Margherita_ConcreteBuilder margheritaBuilder;
  Spicy_ConcreteBuilder spicyPizzaBuilder;
  cook.makePizza (& margheritaBuilder);
  cook.tastePizza ();
  cook.makePizza (& spicyPizzaBuilder);
  cook.tastePizza ();
}
// Ausgabe
// Pizza mit Querteig, Tomatensauce und Mozzarela + Basilikum. // Mmmmmmm.
// Pizza mit Pfannenteig, Tomaten - Chili - Sauce und
// Pepperoni + Salami-Belag. Mmmmmmm.

Fassen wir die Vorteile dieses Musters zusammen:

  • Es gibt viele mögliche Darstellungen, aber nur einen gemeinsamen Eingang.
  • Wir haben ein Standardprotokoll für die Erstellung aller möglichen Darstellungen. Die Schritte dieses Protokolls werden von einer Builder-Schnittstelle klar dargestellt.
  • Für jede Zieldarstellung gibt es eine abgeleitete konkrete Klasse.
  • Es ist für einen Entwickler einfach, eine neue, selbstverwaltete und unabhängige Darstellung (einer Pizza) hinzuzufügen, ohne befürchten zu müssen, etwas anderes zu beschädigen.
  • Der Kunde kann den ConcreteBuilder einfach beim Director registrieren und erhält die erwartete Darstellung.

Der nächste Blog wird eine Kurzanleitung für das Decorator-Designmuster sein. Es ist ein strukturelles Muster, das Sie in Ihrem Wissensspeicher unbedingt haben müssen. 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.