(Credits: Blog von Vishal Chovatiya)
„Um komplexe und komplizierte Objekte stückweise und intuitiver erstellen zu können.”
Das Builder Pattern ist ein Entwurfsmuster, mit dem schrittweise komplexe Objekte mit der richtigen Reihenfolge von Aktionen erstellt werden können. Die Konstruktion wird von einem Director-Objekt gesteuert, das nur den Objekttyp kennen muss, den es erstellen soll.
Das folgende UML-Diagramm beschreibt eine Implementierung des Builder Patterns. Es besteht im Wesentlichen aus vier Teilen:
- Product: Klasse, die das komplexe Objekt repräsentiert, das zu erstellen ist.
- Builder: Abstrakte Basisklasse (oder Schnittstelle) für
Builder-Objekte. Es werden die Schritte definiert, die ausgeführt werden müssen, um ein komplexes Objekt (Produkt) korrekt zu erstellen. Typischerweise ist jeder Schritt eine abstrakte Methode, die durch eine konkrete Implementierung überschrieben wird. - ConcreteBuilder: Stellt eine Implementierung der
Builder-Schnittstelle bereit. EinBuilder-Objekt ist ein Objekt, das andere komplexe Objekte ("Produkte") erstellen kann. - Director: Stellt die Klasse dar, die den Algorithmus steuert, der zum Erstellen des komplexen Objekts verwendet wird.
Abbildung 1: Schematische Darstellung des Builder Patterns.
Quellcode 1 – Sehr einfache Version
Quellcode 2 – Ein etwas ausführlicheres Beispiel
Die Ausgaben des konzeptionellen Beispiels lauten:
Standard basic product:
Product parts: Part A1
Standard full featured product:
Product parts: Part A1, Part B1, Part C1
Custom product:
Product parts: Part A1, Part C1
Das „Real-World” Beispiel zu diesem Entwurfsmuster orientiert sich an den UI-Bibliotheken von Java. Wir implementieren - natürlich stark vereinfacht - einen Layoutmanager. Das Ergebnis könnten Java-Containerobjekte sein, oder - etwas einfacher gestrickt - in unserem Fall HTML-Code.
Folgende Klassen sind an dem Beispiel beteiligt:
Die Klasse HtmlPage steht für das Produkt des Entwurfsmusters, in unserem Fall die zu erstellende HTML-Seite.
Das Produkt enthält einer Reihe von UI-Steuerelementen, wir führen zu diesem Zweck eine
weitere (Hilfs-)Klasse Widget ein. Ein HtmlPage-Objekt besitzt eine Liste mit Widget-Objekten.
Wesentlich bei dieser Klasse ist, dass sie den fertig generierten HTML-Code, der durch einen Layoutmanager erzeugt wird,
enthält. Über eine getter-Methode getHtmlCode ist dieser verfügbar.
Diese Klasse entspricht der abstrakten Klasse Builder.
Die Methode buildPart aus dem allgemeinen UML-Diagramm findet ihre Entsprechung in der Methode addWidget.
Der wiederholte Aufruf der addWidget-Methode "konfiguriert" gewissermaßen das Produkt.
Die getProduct-Methode aus dem allgemeinen UML-Diagramm entspricht der Methode getHtmlPage.
Etwas abweichend vom allgemeinen UML-Diagramm ist hierzu noch ein vorgelagerter Aufruf der render-Methode,
die das eigentliche Ergebnis berechnet.
Diese Klassen sind Ableitungen von der abstrakten Basisklasse LayoutManager.
In ihrem Aufbau sind sie sehr ähnlich,
sie unterscheiden sich lediglich in der Generierung des HTML-Codes.
Je nach der Funktionsweise des Layouts sind die Widgets in einer Reihe, in einer Spalte
und bzw. oder mit einer entsprechenden Kopf- und Fußzeile anzuzeigen.
Zentral in diesen Klassen ist natürlich die konkrete Realisierung der beiden Methoden
render und addWidget.
Die Layouter-Klasse übernimmt die Funktion der Director-Klasse.
Das Pendant zur construct-Methode ist hier die doLayout-Methode.
Die Klasse Widget ist eine Hilfsklasse und verwaltet den erzeugten HTML-Code.
Einen Überblick über die beteiligten Klassen und ihren Zusammenhang mit dem Builder Pattern entnehmen Sie Abbildung 2:
Abbildung 2: Implementierung eines LayoutManagers mit dem Builder Pattern.
Weitere Details zur Umsetzung des Beispiels entnehmen Sie bitte dem Quellcode. Die Ausgaben des „Real-World”-Beispiels lauten:
HTML: HTML-Code for BorderLayoutManager
HTML: HTML-Code for BoxLayoutManager
HTML: HTML-Code for FlowLayout
Bemerkung:
Diese Anwendung des Builder Patterns wird auch als „Named Arguments in C++” bezeichnet.
Wir betrachten nun ein Beispiel, in dem eine Klasse (Klasse Person)
viele Daten (hier: zur Person und zum Beruf) haben kann.
Natürlich könnte man dies alles mit einer einzigen Klasse (hier: Person)
über unterschiedliche Konstruktoren und setter-Methoden abhandeln.
Bei einer Klasse mit sehr vielen Instanzvariablen kann dies bisweilen
unübersichtlich werden:
01: class Person
02: {
03: private:
04: // personal details
05: std::string m_name;
06: std::string m_street_address;
07: std::string m_post_code;
08: std::string m_city;
09: std::string m_country;
10:
11: // employment details
12: std::string m_sector;
13: std::string m_company_name;
14: std::string m_position;
15:
16: Person(const std::string& name) : m_name{ name } {}
17: };Anstatt all diese konstruktionsbezogenen Methoden (Konstruktoren, setter-Methoden)
der Klasse Person hinzuzufügen,
delegieren wir diese Aufgabe an eine separate Klasse – wir nennen sie PersonBuilder.
Studieren Sie den Quellcode der Klasse PersonBuilder genau:
Indem der Konstruktor der Klasse Person privat deklariert ist,
zwingen wir Benutzer der Klasse Person, die
PersonBuilder-Klasse zu verwenden.
Es ist nur die create(const std::string& name)-Schnittstellenmethode an der Klasse
Person verfügbar.
Betrachten Sie den folgenden Quellcode: Es wird der so genannte „Fluent Builder” Programmierstil demonstriert:
Person p{ Person::create("Jack")
.lives("Great Britain")
.at("17 Sloane Street")
.with_postcode("SW1X 9NU")
.in("London")
.works("Information Technology")
.with("Software Manufactur")
.as_a("Consultant")
};Die Anregungen zum konzeptionellen Beispiel finden Sie unter
https://refactoring.guru/design-patterns
und
vor.
Das „Real-World”-Beispiel ist eine Portierung eines entsprechenden C#-Beispiels aus dem Buch von Matthias Geirhos, siehe dazu das Literaturverzeichnis.
