Wenn man mit dem Builder Pattern arbeitet, gelangt man an den Punkt, an dem man komplexe Objekte aufbauen muss. Nehmen wir nun an, dass wir ein Auto erzeugen möchten. Dieses besteht aus den Attributen Motor, Maschine und einer Anzahl Räder. Hierfür verwenden wir nun das nachfolgende Klassenmodell.
Nun kann man für jede Klasse einen entsprechenden Builder generieren lassen. Wenn man sich dabei an das Basispattern hält, sieht das für die Klasse Wheel in etwa so aus:
Der Builder ist als inner static class realisiert und vollzieht demnach die Änderungen in der Klasse Wheel nach, damit nur noch über den Builder gegangen werden kann, um eine Instanz zu erzeugen. Natürlich habe ich hierbei die Möglichkeiten via Reflection ausgelassen.
Wie aber sieht es nun aus, wenn man eine Instanz der Klasse Car erzeugen möchte? Hier kommen wir zu dem Punkt, dass wir die Instanz der Klasse Wheel der Instanz der Klasse Car hinzufügen wollen.
Dieser Quelltext ist nicht sonderlich schön und auf keinen Fall kompakt. Wie also kann man hier das Builder Pattern anpassen, damit man auf der einen Seite möglichst wenig von dem Builder selbst von Hand schreiben muss und auf der anderen Seite bei der Verwendung mehr Komfort bekommt?
WheelListBuilder
Gehen wir zuerst einen kleinen Umweg. Um zum Beispiel sicherzustellen, dass man einem Auto nur vier Räder hinzufügen kann, kann man z. B. einen WheelListBuilder erzeugen. Hier kann man z. B. in der Methode build() überprüfen, ob vier Instanzen der Klasse Wheel vorhanden sind.
Nun sieht unser Beispiel von vorhin wie folgt aus:
Als nächstes verbinden wir den Builder der Klasse Wheel und die Klasse WheelListBuilder. Das Ziel ist es, ein Fluent API zu erhalten, damit wir nicht die Instanzen der Klasse Wheel einzeln erzeugen und dann diese mit der Methode addWheel(Wheel w) dem WheelListBuilder hinzufügen müssen. Es soll dann für den Entwickler in der Verwendung wie folgt aussehen:
Was hier also passiert, ist folgendes: Sobald die Methode addWheel() aufgerufen wird, soll eine neue Instanz der Klasse WheelBuilder zurückgegeben werden. Die Methode addWheelToList() erzeugt die Instanz der Klasse Wheel und fügt sie der List hinzu.
Um das zu erreichen, muss man die beiden beteiligten Builder modifizieren. Auf der Seite des WheelBuilder kommt die Methode addWheelToList() hinzu. Diese fügt die Instanz der Klasse Wheel dem WheelListBuilder hinzu und liefert die Instanz der Klasse WheelListBuilder zurück.
Auf der Seite der Klasse WheelListBuilder wird lediglich die Methode addWheel() hinzugefügt.
Wenn wir nun dieses auf die anderen Builder übertragen, kommen wir zu einem recht ansehnlichen Ergebnis:
NestedBuilder
Bisher wurden die Builder von Hand einzeln modifiziert. Dieses kann man aber recht einfach generisch implementieren. Es handelt sich lediglich um einen Baum von Buildern. Jeder Builder kennt demnach seine Kinder und seinen Vater. Die dafür notwendigen Implementierungen sind in der Klasse NestedBuilder zu finden. Hierbei wird angenommen, dass die Methoden zum Setzen von Attributen immer mit with beginnen. Da dies aber bei den meisten Generatoren für Builder so zu sein scheint, ist hier keine manuelle Anpassung notwendig.
Nun können einem Parent die spezifischen Methoden für die Verbindungen zu den Kindern hinzugefügt werden. Ein Ableiten von NestedBuilder ist nicht erforderlich.
Und bei den Kindern sieht es wie folgt aus: Hier muss lediglich von NestedBuilder abgeleitet werden.
Die Verwendung ist dann, wie in dem vorherigen Beispiel gezeigt, sehr kompakt.
Fazit
Natürlich ist auch eine beliebige Kombination möglich. Das bedeutet, dass ein Proxy Vater und Kind gleichzeitig sein kann. Dem Aufbau komplexer Strukturen steht nun nichts mehr im Wege.
Weitere Artikel in diesem Themenbereich
Entdecke spannende weiterführende Themen und lass dich von der codecentric Welt inspirieren.
Blog-Autor*in
Sven Ruppert
Du hast noch Fragen zu diesem Thema? Dann sprich mich einfach an.
Du hast noch Fragen zu diesem Thema? Dann sprich mich einfach an.