Builder:Objekt-Erzeugung
Objekt-Erzeugung
Wann soll das Objekt -- das der Builder bauen soll -- erzeugt werden? Auf diese Frage giebt es zwei Antworten: "Up front" oder "in time". Für beide Varianten giebt es Argumente, welche man wählt hängt von den Umständen ab. Betrachten wir also beide...
Up font
Was so viel heißt: Wir erzeugen das Objekt und füllen die Daten direkt hinein. Die Erzeugung findet entweder bei der Deklaration des entsprechenden Feldes statt oder im Konstruktor des Builders. Zu bevorzugen ist der erste Fall, das kann zum Beispiel so aussehen:
Foo foo == new Foo(); FooBuilder withName(String name) { foo.setName(name); return this; }
Das Objekt das der Builder erzeugt wird erzeugt und von den with-Methoden direkt befüllt. Der Vorteil ist ein sehr geringer Overhead, weil kein Zwischenspeicher für gesetzte Werte benötigt wird. Die build-Methode kann das Objekt ohne weitere Aktionen ausliefern. Wenn es darum geht, Entities zu erzeugen und zu befüllen ist das normalerweise das Mittel der Wahl.
in time
Die Alternative ist, das Objekt erst dann zu erzeugen, wenn es tatsächlich benötigt wird, in der Regel geschieht das dann in der build-Methode. Der offensichtliche Nachteil ist, daß die with-Methoden ihre Daten nicht direkt in das zu erzeugende Objekt füllen können. Es wird ein Buffer -- üblicherweise in Form von Builder-Feldern benötigt:
private String name; FooBuilder withName(String name) { this.name = name; return this; } Foo build() { Foo foo = new Foo(); foo.setName(this.name); return foo; }
Wann was?
Wie man sieht ist der Aufwand bei der verzögerten Erzeugung spürbar höher als bei der Befüllung des sofort erzeugten Objekt. Warum sollte man sich das antun? Man wird sich für die vergögerte Variante in der Regel nur dann entscheiden, wenn es gute Gründe für die verzögerte Objekt-Erzeugung giebt. Wenn man etwa das Objekt nur dann anlegen möchte, wenn tatsächlich alle Daten vorhanden sind bzw. alle Daten korrekt sind. Oder wenn die Erzeugung des Objektes besonders teuer ist.
Und es giebt einen Fall, in dem es gar nicht anders geht. Nämlich wenn das zu erzeugende Objekt die Daten -- oder zumindest einige der Daten -- im Konstruktor verlangt. Diese Variante wird im nächsten Abschnitt beschrieben.
Einen wichtigen Punkt muß man dabei unbedingt berücksichtigen. Wird das Objekt sofort erzeugt und von der Abschlußmethode des Builders geliefert, verändert jeder nachfolgende Aufruf einer with-Methode des Builders dazu, daß das eben erzeugte Objekt nachträglich verändert wird!
Die verzögerte Erzeugung kann verwendet werden, wenn der Builder als (Massen-)Factory verwendet werden soll. Man konfiguriert damit ein Objekt, erzeugt es, verwendet die Konfiguration als Grundlage für das nächste Objekt und so fort.
der Fall des ekelhaften Konstrukturs
Ein formidables Beispiel liefert uns freundlicherweise der JDK. Es handelt sich um die Klasse NewCookie. Der schlimmste Konstruktur -- es giebt im JDK 1.7 sieben Stück davon -- könnte im Aufruf so aussehen:
cookie = NewCookie("myCookie", null, null, 4711, "unused", 10, null, true, true);
Das ist so lesbar wie ansprechend... In diesem Falle bekommt der Builder für jeden Parameter ein Feld und eine with-Methode. Erst in der build-Methode erzeugt man den Cookie mit den Werten der Builder-Felder. Auf diese Weise werden im Builder-Aufruf nur die erforderlichen Werte gesetzt, die übrigen ggf. mit default-Werten vorbelegt und der Benutzer braucht nicht nach dem passenden Konstruktor suchen.
In diesem Falle ist es natürlich auch sinnvoll, in der build-Methode zu prüfen, ob alles da ist was benötigt wird.