14. Juni 2013 | 5 min lesezeit

Wie erzeuge ich ein Objekt?

In meinem letzten Blog-Eintrag habe ich über Setter geschrieben und dass man sie nicht in seinem Domänenmodell verwenden sollte. Die Idee dahinter ist, dass ein Objekt zu jedem Zeitpunkt seiner Existenz valide und konsistent sein sollte.

Eine leeres Adress-Objekt ist weder valide noch konsistent. Also erlaube ich die Erzeugung eines leeren Adress-Objektes gar nicht erst. Tatsächlich darf ein Adress-Objekt nicht mal veränderbar sein. Wenn man ein Attribut ändern würde, wäre es eine komplett neue Adresse und – das Wort neu deutet bereits darauf hin – sollte daher ein neues Objekt sein. OK, wenn eine Adresse nicht änderbar ist, wie kann man sie erzeugen, so dass sie von Anfang an valide und konsistent sind? Nebenbei sollten auch änderbare Objekte zu jedem Zeitpunkt konsistent sein. Die einfachste Möglichkeit, das zu erreichen, ist es, einen Konstruktur zur Verfügung zu stellen, der für jedes nicht optionale Attribut des Objektes einen Parameter hat.

Innerhalb des Konstruktors kann man nun sicherstellen, dass das Objekt von Anfang an valide und konsistent ist. Wenn einer der Parameter inkonsistent ist, wirft der Konstruktor eine Exception. So weit so gut. Allerdings kann man an dem Beispiel erkennen, dass dieses Pattern mit wachsender Anzahl von Attributen immer hässlicher wird, weil die Anzahl der Konstruktor-Parameter wächst (nicht nötig zu erwähnen, dass das Checkstyle-Plugin sich beschweren sollte). Also, wie kann man das lösen?

Meine Lieblingslösung für dieses Problem ist das Builder Pattern. Dem Single Responsibility Principle folgend, ist der Builder nur dafür verantwortlich, dass Objekt zu erzeugen. Es gibt verschiedene Arten von Buildern, die ich jetzt vorstellen möchte.

Der JavaBean Style Builder

Der erste Ansatz eines Builders sieht einer einfachen JSF Bean sehr ähnlich (tatsächlich entspricht er dem JavaBeans Standard). Man erzeugt einfach Getter und Setter für jedes Attribut der Adresse.

Leser meines letzten Blog-Eintrags werden jetzt fragen: Auf welche Art ist der Builder besser als das Objekt mit generierten Gettern und Settern, dass ich da so verteufelt habe? Die Antwort ist einfach: Der Builder ist nur dafür verantwortlich, das Objekt zu erzeugen. Die tatsächliche Validierung der Parameter kann an einer einzigen Stelle im Code stattfinden und diese Stelle stellt sicher, dass nur valide und konsistente Adress-Objekte erzeugt werden. Wir werden später sehen, wo im Code diese Stelle ist (es gibt tatsächlich mehrere Möglichkeiten). The answer is simple: The builder is just responsible to create a domain object. So kann man diese Art von Buildern benutzen:

Diese Art von Buildern ist in Ordnung und sie kann sogar in JSF verwendet werden, um ein Objekt anzulegen. Allerdings könnte der Code etwas weniger verbose sein.

Der Fluent-API-Style Builder

Wir können den Code zum Anlegen einer Adresse deutlich reduzieren, indem wir einfach nur das Pattern „Method Chaining“ für den Builder einführen. Das bedeutet, dass jede Methode des Builders this zurückgibt. Das ermöglicht uns, die meisten Referenzen auf die Builder-Variable aus dem Beispiel oben zu entfernten:

Und wir können die Lesbarkeit des Codes weiter erhöhen, indem wir eine Fluent API verwenden:

Ein kleines Problem ist, dass man dem Code nicht ansieht, dass er eine Adresse erzeugt. Man kann dies weiter herausstellen, in dem man eine statische Methode innerhalb des Builders zur Verfügung stellt:

Wenn man diese Methode verwendet und auch noch über einen statischen Import importiert, wird der obige Adresserzeugungs-Code deutlich lesbarer:

Jetzt haben wir zwei verschiedene Arten von Buildern und deren Verwendung kennengelernt. Ich glaube, es gibt keine zwei Meinungen darüber, dass die zweite Variante eleganter ist. Wie auch immer, beide Varianten haben die Methode build(), die ein Adress-Objekt zurückliefert. Bisher habe ich nirgends erwähnt, wie man diese Methode implementieren kann. Und das wird auch nicht so einfach, weil wir ja nicht wollen, dass die Adresse einen Konstruktor mit einer solchen Anzahl Parameter bekommt und wir wollen auch keine invalide oder inkonsistente Adresse erzeugen. Es gibt drei Varianten, das zu erreichen.

Der offensichtliche Ansatz

Das Problem ist, dass wir nicht so viele Konstruktor-Parameter haben wollen und wir wollen unsere Adresse an einer Stelle im Code valide und konsistent erzeugen? Also übergeben wir dem Konstruktor einfach den Builder und sind fertig.

Der erste Nachteil dieses Ansatzes ist es, dass unser Builder nicht nur die Fluent API benötigt, sondern zusätzlich noch Getter für alle Attribute. Das ist allerdings nicht zu tragisch. Was tatsächlich unschön ist an diesem Ansatz, ist die Abhängigkeit der Adresse zu ihrem Builder. Was können wir also tun, um die Abhängigkeit umzudrehen? Wir können die komplette Erzeugungslogik in die build()-Methode des Builders verschieben. Das Problem ist: Wie kann der Builder ein Adress-Objekt erzeugen ohne einen Konstruktor zu verwenden (und wir wollen weder einen Konstruktor mit vielen Parametern noch einen Konstruktor, der den Builder als Parameter bekommt, wegen der Abhängigkeit)?

Package-access Builder

Wir können den Builder in dasselbe Java-Package legen wie die Adresse und package-private (ohne Access-Modifier) Setter für die Attribute zur Verfügung stellen. Auf diese Weise stellen wir sicher, dass kein Code von außerhalb des Packages die Setter verwenden kann, der Builder aber schon. Man sollte nicht vergessen, auch den Konstruktor der Adresse package-private zu deklarieren.

Dieser Ansatz funktioniert ganz gut und die Erzeugungslogik für die Adresse ist auch an einer Stelle im Code, nämlich in der build()-Methode. Was ich an diesem Ansatz nicht mag, ist die Wiedereinführung der Setter, auch wenn sie nur package-private sind und nicht von außerhalb genutzt werden können. Es ist nur ein Gefühl: Wenn ein Programmierer ein Attribut ändern will, ist es einfach zu einfach nur ein public vor die Methode zu setzen und wir sind wieder an dem Punkt, über den ich in meinem letzten Blog-Eintrag geschrieben habe. Deshalb mag ich diesen Ansatz nicht so gerne.

Der Inner Class Builder

Denken wir zurück daran, was unser eigentliches Problem ist: Wie kann der Builder eine Adresse erzeugen und ihre Attribute setzen ohne einen Konstruktor zu verwenden, der alle diese Attribute erhält und – im Gegensatz zum letzten Ansatz – ohne Setter. Also, das kann erreicht werden, indem der Builder eine innere Klasse der Adresse wird.

Dieser Ansatz funktioniert auch ganz gut. Er enthält allerdings viel Code, den ein Junior Entwickler wahrscheinlich noch nie zuvor gesehen hat, wie new Address().new Builder() und Address.this. Es ist also der Ansatz, der am Anfang am schwierigsten zu verstehen ist, aber wenn man ihn einmal verstanden hat, funktioniert er auch sehr gut. Der Nachteil ist, dass die tatsächliche Erzeugung des Adress-Objektes direkt vor der Erzeugung des Builders passiert. Das ist aber nicht weiter tragisch, weil Code von außerhalb das Objekt erst verwenden kann, nachdem es vom Builder validiert worden ist.

Egal welchen der vorgestellten Ansätze man verwendet, wenn man bei JSF landet, hat man das Problem, dass man keinen der Builder direkt an die UI binden kann (außer dem ersten, der sich an die JavaBeans-Konventionen hält und nicht so elegant ist). In einem meiner nächsten Blog-Einträge werde ich zeigen, was man tun kann, um einen Fluent Builder direkt in JSF zu verwenden. Stay tuned.


Keine Kommentare

Kontakt

OPEN KNOWLEDGE GmbH

Standort Oldenburg:
Poststraße 1, 26122 Oldenburg

Standort Essen:
II. Hagen 7, 45127 Essen