Warum benötigen wir eine Builder-Klasse, wenn wir ein Builder-Muster implementieren?

31

Ich habe viele Implementierungen des Builder-Musters gesehen (hauptsächlich in Java). Alle von ihnen haben eine Entity-Klasse (sagen wir eine PersonKlasse) und eine Builder-Klasse PersonBuilder. Der Builder "stapelt" verschiedene Felder und gibt ein new Personmit den übergebenen Argumenten zurück. Warum benötigen wir explizit eine Builder-Klasse, anstatt alle Builder-Methoden in der PersonKlasse selbst zu platzieren?

Beispielsweise:

class Person {

  private String name;
  private Integer age;

  public Person() {
  }

  Person withName(String name) {
    this.name = name;
    return this;
  }

  Person withAge(int age) {
    this.age = age;
    return this;
  }
}

Ich kann es einfach sagen Person john = new Person().withName("John");

Warum braucht man eine PersonBuilderKlasse?

Der einzige Vorteil, den ich sehe, ist, dass wir die PersonFelder als deklarieren können, um die finalUnveränderlichkeit zu gewährleisten.

Boyan Kushlev
quelle
4
Hinweis: Der normale Name für dieses Muster lautet chainable setters: D
Mooing Duck
4
Prinzip der Einzelverantwortung. Wenn Sie die Logik in die Klasse Person
einfügen,
1
Ihr Vorteil kann in beiden Fällen bestehen: Ich kann withNameeine Kopie der Person zurücksenden, wobei nur das Namensfeld geändert wurde. Mit anderen Worten, Person john = new Person().withName("John");könnte funktionieren, auch wenn Personunveränderlich ist (und dies ist ein verbreitetes Muster in der funktionalen Programmierung).
Brian McCutchon
9
@MooingDuck: Ein weiterer gebräuchlicher Begriff dafür ist eine flüssige Benutzeroberfläche .
Mac
2
@Mac Ich denke, die flüssige Benutzeroberfläche ist etwas allgemeiner - Sie vermeiden voidMethoden. Wenn beispielsweise Personeine Methode ihren Namen ausgibt, können Sie sie dennoch mit einer Fluent-Schnittstelle verketten person.setName("Alice").sayName().setName("Bob").sayName(). Übrigens kommentiere ich die in JavaDoc mit genau Ihrem Vorschlag @return Fluent interface- es ist allgemein und klar genug, wenn es auf eine Methode angewendet wird, die return thisam Ende ihrer Ausführung ausgeführt wird, und es ist ziemlich klar. Ein Builder wird also auch eine flüssige Benutzeroberfläche erstellen.
VLAZ

Antworten:

27

So können Sie unveränderlich sein UND gleichzeitig benannte Parameter simulieren .

Person p = personBuilder
    .name("Arthur Dent")
    .age(42)
    .build()
;

So bleiben Ihre Finger von der Person fern, bis der Status festgelegt ist, und Sie können ihn nach dem Festlegen nicht mehr ändern, obwohl jedes Feld eindeutig gekennzeichnet ist. Sie können dies nicht mit nur einer Klasse in Java tun.

Sie sprechen anscheinend über Josh Blochs Builder Pattern . Dies sollte nicht mit dem Builder-Muster der Viererbande verwechselt werden . Das sind verschiedene Tiere. Sie lösen beide Bauprobleme, aber auf ziemlich unterschiedliche Weise.

Natürlich können Sie Ihr Objekt auch ohne Verwendung einer anderen Klasse erstellen. Aber dann muss man sich entscheiden. Sie verlieren entweder die Fähigkeit, benannte Parameter in Sprachen zu simulieren, die diese nicht haben (wie Java), oder Sie verlieren die Fähigkeit, während der gesamten Lebensdauer des Objekts unveränderlich zu bleiben.

Unveränderliches Beispiel, hat keine Namen für Parameter

Person p = new Person("Arthur Dent", 42);

Hier erstellen Sie alles mit einem einzigen einfachen Konstruktor. Dadurch bleiben Sie unveränderlich, verlieren jedoch die Simulation der benannten Parameter. Das ist bei vielen Parametern schwer zu lesen. Computer kümmern sich nicht darum, aber es ist hart für die Menschen.

Beispiel für einen simulierten benannten Parameter mit herkömmlichen Setzern. Nicht unveränderlich.

Person p = new Person();
p.name("Arthur Dent");
p.age(42);

Hier erstellen Sie alles mit Setzern und simulieren benannte Parameter, aber Sie sind nicht mehr unveränderlich. Jede Verwendung eines Setters ändert den Objektstatus.

Durch Hinzufügen der Klasse erhalten Sie also die Möglichkeit, beides zu tun.

Die Validierung kann in durchgeführt werden, build()wenn ein Laufzeitfehler für ein fehlendes Altersfeld für Sie ausreicht. Sie können das aktualisieren und das erzwingen, age()das mit einem Compiler-Fehler aufgerufen wird. Nur nicht mit dem Josh Bloch Builder Pattern.

Dafür benötigen Sie eine interne Domain Specific Language (iDSL).

So können Sie verlangen, dass sie anrufen age()und name()bevor Sie anrufen build(). Aber Sie können es nicht einfach tun, indem Sie thisjedes Mal zurückkehren. Jedes zurückgegebene Objekt gibt ein anderes Objekt zurück, das Sie zwingt, das nächste Objekt aufzurufen.

Die Verwendung könnte folgendermaßen aussehen:

Person p = personBuilder
    .name("Arthur Dent")
    .age(42)
    .build()
;

Aber dieses:

Person p = personBuilder
    .age(42)
    .build()
;

verursacht einen Compilerfehler, da age()nur der Typ gültig ist, der von zurückgegeben wird name().

Diese iDSLs sind extrem leistungsfähig ( z. B. JOOQ- oder Java8-Streams ) und sehr benutzerfreundlich , insbesondere, wenn Sie eine IDE mit Code-Vervollständigung verwenden, die Einrichtung ist jedoch aufwändig . Ich würde empfehlen, sie für Dinge zu speichern, gegen die einiges an Quellcode geschrieben wird.

kandierte_orange
quelle
3
Und dann fragt jemand , warum Sie haben den Namen zu schaffen , vor dem Alter, und die einzige Antwort „ weil die kombinatorische Explosion“. Ich bin mir ziemlich sicher, dass man das in der Quelle vermeiden kann, wenn man geeignete Vorlagen wie C ++ hat oder weiterhin für alles einen anderen Typ verwendet.
Deduplizierer
1
Eine undichte Abstraktion erfordert das Wissen über eine zugrunde liegende Komplexität, um wissen zu können, wie man sie verwendet. Das sehe ich hier. Jede Klasse, die nicht weiß, wie sie sich selbst erstellen soll, ist notwendigerweise an den Builder gekoppelt, der weitere Abhängigkeiten aufweist (dies ist der Zweck und die Art des Builder-Konzepts). Einmal (nicht im Bandcamp) musste ein Objekt instanziiert werden, was 3 Monate dauerte, um einen solchen Cluster-Frack rückgängig zu machen. Das ist kein Scherz.
Radarbob
Einer der Vorteile von Klassen, die keine Konstruktionsregeln kennen, ist, dass sie sich nicht darum kümmern, wenn sich diese Regeln ändern. Ich kann nur eine hinzufügen AnonymousPersonBuilder, die ihre eigenen Regeln hat.
candied_orange
Klassen- / Systemdesign zeigt selten eine tiefe Anwendung von "Kohäsion maximieren und Kopplung minimieren". Legionen von Design und Laufzeit sind erweiterbar und Fehler können nur behoben werden, wenn OO-Maximen und -Richtlinien mit dem Gefühl angewendet werden, fraktal zu sein, oder "es sind Schildkröten, die den ganzen Weg nach unten".
Radarbob
1
@radarbob Die Klasse, die erstellt wird, weiß immer noch, wie sie sich selbst erstellt. Der Konstrukteur ist dafür verantwortlich, die Integrität des Objekts sicherzustellen. Die Builder-Klasse ist lediglich ein Helfer für den Konstruktor, da der Konstruktor häufig eine große Anzahl von Parametern akzeptiert, was für eine sehr schöne API nicht geeignet ist.
Nein U
57

Gründe für die Verwendung / Bereitstellung einer Builder-Klasse:

  • Unveränderliche Objekte herstellen - der Nutzen, den Sie bereits erkannt haben. Nützlich, wenn die Konstruktion aus mehreren Schritten besteht. FWIW, Unveränderlichkeit sollte ein wichtiges Werkzeug in unserem Bestreben sein, wartbare und fehlerfreie Programme zu schreiben.
  • Wenn die Laufzeitdarstellung des endgültigen (möglicherweise unveränderlichen) Objekts für das Lesen und / oder die Speicherplatznutzung optimiert ist, jedoch nicht für die Aktualisierung. String und StringBuilder sind hier gute Beispiele. Das wiederholte Verketten von Zeichenfolgen ist nicht sehr effizient. Daher verwendet der StringBuilder eine andere interne Darstellung, die sich zum Anhängen eignet - jedoch nicht so gut für die Speicherplatznutzung und nicht so gut zum Lesen und Verwenden wie die reguläre String-Klasse.
  • Konstruierte Objekte klar von im Bau befindlichen Objekten trennen. Dieser Ansatz erfordert einen klaren Übergang von im Bau befindlichen zu gebauten. Für den Verbraucher gibt es keine Möglichkeit, ein im Bau befindliches Objekt mit einem konstruierten Objekt zu verwechseln: Das Typsystem wird dies erzwingen. Das heißt, manchmal können wir diesen Ansatz verwenden, um sozusagen in die Erfolgsgrube zu geraten, und wenn wir Abstraktion für andere (oder uns selbst) zur Verwendung machen (wie eine API oder eine Schicht), kann dies ein sehr guter Ansatz sein Sache.
Erik Eidt
quelle
4
In Bezug auf den letzten Aufzählungspunkt. Die Idee ist also, dass a PersonBuilderkeine Getter hat, und die einzige Möglichkeit, die aktuellen Werte zu überprüfen, besteht darin, aufzurufen .Build(), um a zurückzugeben Person. Auf diese Weise kann .Build ein ordnungsgemäß erstelltes Objekt validieren, richtig? Dh dies ist der Mechanismus, der die Verwendung eines "im Bau befindlichen" Objekts verhindern soll?
AaronLS
@ AaronLS, ja, sicher; Eine komplexe Validierung kann in eine BuildOperation eingefügt werden , um fehlerhafte und / oder unterkonstruierte Objekte zu verhindern.
Erik Eidt
2
Sie können Ihr Objekt auch innerhalb seines Konstruktors validieren. Die Präferenz kann persönlich sein oder von der Komplexität abhängen. Was auch immer gewählt wird, der wichtigste Punkt ist, dass Sie wissen, dass Ihr unveränderliches Objekt richtig aufgebaut ist und keinen unerwarteten Wert hat, sobald Sie es haben. Ein unveränderliches Objekt mit einem falschen Status sollte nicht auftreten.
Walfrat
2
@AaronLS Ein Builder kann Getter haben. das ist nicht der Punkt. Dies ist nicht getAgeerforderlich. Wenn ein Builder über Getter verfügt, müssen Sie sich darüber im Klaren sein, dass der Aufruf des Builders möglicherweise zurückgegeben wird, nullwenn diese Eigenschaft noch nicht angegeben wurde. Im Gegensatz dazu kann die PersonKlasse die Invariante erzwingen, dass Alter niemals sein kann null, was unmöglich ist, wenn Builder und tatsächliches Objekt gemischt werden, wie im new Person() .withName("John")Beispiel des OP , das glücklich Personein Alter ohne Alter erzeugt. Dies gilt auch für veränderbare Klassen, da Setter zwar Invarianten, aber keine Anfangswerte erzwingen können.
Holger
1
@Holger Ihre Punkte sind wahr, aber hauptsächlich unterstützen Sie das Argument, dass ein Builder Getter vermeiden sollte.
user949300
21

Ein Grund wäre, sicherzustellen, dass alle übergebenen Daten den Geschäftsregeln entsprechen.

In Ihrem Beispiel wird dies nicht berücksichtigt. Nehmen wir jedoch an, dass jemand eine leere Zeichenfolge oder eine Zeichenfolge aus Sonderzeichen übergeben hat. Sie sollten eine Art Logik verwenden, um sicherzustellen, dass der Name tatsächlich gültig ist (was eigentlich eine sehr schwierige Aufgabe ist).

Sie könnten das alles in Ihre Person-Klasse aufnehmen, besonders wenn die Logik sehr klein ist (zum Beispiel nur sicherstellen, dass ein Alter nicht negativ ist), aber wenn die Logik wächst, ist es sinnvoll, sie zu trennen.

Diakon
quelle
7
Das Beispiel in der Frage liefert eine einfache Regelverletzung: Es ist kein Alter fürjohn
Caleth 22.10.18
6
+1 Und es ist sehr schwierig, Regeln für zwei Felder gleichzeitig ohne die Builder-Klasse auszuführen (Felder X + Y können nicht größer als 10 sein).
Reginald Blue
7
@ReginaldBlue Es ist noch schwieriger, wenn sie durch "x + y ist gerade" gebunden sind. Unsere Ausgangsbedingung mit (0,0) ist OK. Der Status (1,1) ist ebenfalls OK. Es gibt jedoch keine Folge von Aktualisierungen einzelner Variablen, die den Status gültig halten und uns von (0,0) auf (1) bringen 1).
Michael Anderson
Ich glaube, das ist der größte Vorteil. Alle anderen sind nett.
Giacomo Alzetta
5

Ein bisschen anders, als ich es in anderen Antworten sehe.

Der withFooAnsatz hier ist problematisch, da sie sich wie Setter verhalten, aber so definiert sind, dass es so aussieht, als ob die Klasse die Unveränderlichkeit unterstützt. Wenn eine Methode in Java-Klassen eine Eigenschaft ändert, ist es üblich, die Methode mit 'set' zu starten. Ich habe es als Standard noch nie geliebt, aber wenn Sie etwas anderes tun, wird es die Leute überraschen und das ist nicht gut. Es gibt eine andere Möglichkeit, die Unveränderlichkeit mit der hier verfügbaren Basis-API zu unterstützen. Beispielsweise:

class Person {
  private final String name;
  private final Integer age;

  private Person(String name, String age) {
    this.name = name;
    this.age = age;
  }  

  public Person() {
    this.name = null;
    this.age = null;
  }

  Person withName(String name) {
    return new Person(name, this.age);
  }

  Person withAge(int age) {
    return new Person(this.name, age);
  }
}

Es bietet nicht viel, um nicht ordnungsgemäß erstellte Objekte zu verhindern, verhindert jedoch Änderungen an vorhandenen Objekten. Es ist wahrscheinlich albern für so etwas (und so ist der JB Builder). Ja, Sie werden mehr Objekte erstellen, aber das ist nicht so teuer, wie Sie vielleicht denken.

Diese Vorgehensweise wird meistens bei gleichzeitigen Datenstrukturen wie CopyOnWriteArrayList verwendet . Und dies deutet darauf hin, warum Unveränderlichkeit wichtig ist. Wenn Sie Ihren Code threadsicher machen möchten, sollte die Unveränderlichkeit fast immer berücksichtigt werden. In Java darf jeder Thread einen lokalen Cache mit variablem Status führen. Damit ein Thread die in anderen Threads vorgenommenen Änderungen sehen kann, muss ein synchronisierter Block oder eine andere Nebenläufigkeitsfunktion verwendet werden. Jede dieser Methoden erhöht den Aufwand für den Code. Aber wenn Ihre Variablen endgültig sind, gibt es nichts zu tun. Der Wert wird immer so sein, wie er initialisiert wurde, und daher sehen alle Threads das Gleiche, egal was passiert.

JimmyJames
quelle
2

Ein weiterer Grund, der hier nicht explizit erwähnt wurde, ist, dass die build()Methode überprüfen kann, ob alle Felder 'Felder sind, die gültige Werte enthalten (entweder direkt festgelegt oder von anderen Werten anderer Felder abgeleitet). Dies ist wahrscheinlich der wahrscheinlichste Fehlermodus das würde sonst passieren.

Ein weiterer Vorteil ist, dass Ihr PersonObjekt eine einfachere Lebensdauer und eine einfachere Menge von Invarianten hat. Sie wissen, dass Sie Person pein p.nameund ein gültiges haben, sobald Sie ein haben p.age. Keine Ihrer Methoden muss so konzipiert sein, dass sie Situationen wie "Nun, was ist, wenn das Alter festgelegt ist, aber kein Name, oder was ist, wenn der Name festgelegt ist, aber kein Alter?" Dies reduziert die Gesamtkomplexität der Klasse.

Alexander - Setzen Sie Monica wieder ein
quelle
"[...] kann überprüfen, ob alle Felder gesetzt sind" Der Punkt des Builder-Musters ist, dass Sie nicht alle Felder selbst setzen müssen und auf sinnvolle Standardeinstellungen zurückgreifen können. Wenn Sie trotzdem alle Felder festlegen müssen, sollten Sie dieses Muster möglicherweise nicht verwenden!
Vincent Savard
@VincentSavard In der Tat, aber meiner Einschätzung nach ist dies die häufigste Verwendung. Um zu überprüfen, ob einige "Kern" -Werte festgelegt wurden, und um einige optionale Werte herum eine ausgefallene Behandlung durchzuführen (Festlegen von Standardwerten, Ableiten ihres Werts von anderen Werten usw.).
Alexander - Reinstate Monica
Anstatt zu sagen, dass alle Felder gesetzt sind, ändere ich dies in "Überprüfen, dass alle Felder gültige Werte enthalten [manchmal abhängig von den Werten anderer Felder]" (dh, ein Feld lässt möglicherweise nur bestimmte Werte abhängig vom Wert zu eines anderen Feldes). Dies könnte in jedem Setter geschehen, aber es ist einfacher für jemanden, das Objekt einzurichten, ohne Ausnahmen auszulösen, bis das Objekt fertiggestellt ist und erstellt werden kann.
Yitzih
Der Konstruktor der Klasse, die erstellt wird, ist letztendlich für die Gültigkeit seiner Argumente verantwortlich. Die Validierung im Builder ist bestenfalls redundant und kann leicht nicht mit den Anforderungen des Konstruktors synchronisiert werden.
Nein U
@ TKK In der Tat. Aber sie bestätigen verschiedene Dinge. In vielen Fällen, in denen ich Builder verwendet habe, bestand die Aufgabe des Builders darin, die Eingaben zu erstellen, die schließlich an den Konstruktor übergeben werden. Beispiel: Der Builder ist entweder mit a URI, a Fileoder a konfiguriert FileInputStreamund verwendet alles, was bereitgestellt wurde, um a zu erhalten FileInputStream, das schließlich als arg
Alexander - Reinstate Monica
2

Ein Builder kann auch so definiert werden, dass er eine Schnittstelle oder eine abstrakte Klasse zurückgibt. Mit dem Builder können Sie das Objekt definieren, und der Builder kann festlegen, welche konkrete Unterklasse zurückgegeben werden soll, z. B. basierend auf den festgelegten Eigenschaften oder den festgelegten Eigenschaften.

Ed Marty
quelle
2

Mit dem Builder-Muster wird das Objekt Schritt für Schritt erstellt, indem die Eigenschaften festgelegt werden. Wenn alle erforderlichen Felder festgelegt sind, wird das endgültige Objekt mithilfe der Erstellungsmethode zurückgegeben. Das neu erstellte Objekt ist unveränderlich. Hier ist vor allem zu beachten, dass das Objekt nur zurückgegeben wird, wenn die endgültige Erstellungsmethode aufgerufen wird. Dadurch wird sichergestellt, dass alle Eigenschaften für das Objekt festgelegt sind und sich das Objekt daher nicht im inkonsistenten Zustand befindet, wenn es von der Builder-Klasse zurückgegeben wird.

Wenn wir die Builder-Klasse nicht verwenden und alle Builder-Klassenmethoden direkt der Person-Klasse selbst zuweisen, müssen wir zuerst das Objekt erstellen und dann die Setter-Methoden für das erstellte Objekt aufrufen, was zu einem inkonsistenten Zustand des Objekts zwischen der Erstellung führt von Objekt und Festlegen der Eigenschaften.

Durch die Verwendung der Builder-Klasse (dh einer anderen externen Entität als der Person-Klasse selbst) stellen wir sicher, dass sich das Objekt niemals im inkonsistenten Zustand befindet.

Shubham Bondarde
quelle
@DawidO Du hast meinen Standpunkt verstanden. Das Hauptaugenmerk, das ich hier zu setzen versuche, liegt auf dem inkonsistenten Zustand. Dies ist einer der Hauptvorteile der Verwendung von Builder-Mustern.
Shubham Bondarde
2

Builder-Objekt wiederverwenden

Wie bereits erwähnt, sind die Unveränderlichkeit und die Überprüfung der Geschäftslogik aller Felder zur Validierung des Objekts die Hauptgründe für ein separates Builder-Objekt.

Die Wiederverwendbarkeit ist jedoch ein weiterer Vorteil. Wenn ich viele sehr ähnliche Objekte instanziieren möchte, kann ich kleine Änderungen am Builder-Objekt vornehmen und mit der Instantiierung fortfahren. Das Builder-Objekt muss nicht neu erstellt werden. Durch diese Wiederverwendung kann der Builder als Vorlage zum Erstellen vieler unveränderlicher Objekte fungieren. Dies ist ein kleiner Vorteil, der jedoch nützlich sein kann.

yitzih
quelle
1

Eigentlich Sie können die Builder - Methoden auf Ihre Klasse haben sich, und noch Unveränderlichkeit haben. Dies bedeutet lediglich, dass die Builder-Methoden neue Objekte zurückgeben, anstatt das vorhandene zu ändern.

Dies funktioniert nur, wenn es eine Möglichkeit gibt, ein erstes (gültiges / nützliches) Objekt zu erhalten (z. B. von einem Konstruktor, der alle erforderlichen Felder festlegt, oder einer Factory-Methode, die Standardwerte festlegt). Die zusätzlichen Builder-Methoden geben dann geänderte Objekte basierend zurück auf der bestehenden. Diese Builder-Methoden müssen sicherstellen, dass Sie unterwegs keine ungültigen / inkonsistenten Objekte erhalten.

Das bedeutet natürlich, dass Sie viele neue Objekte haben werden, und Sie sollten dies nicht tun, wenn die Erstellung Ihrer Objekte teuer ist.

Ich habe das im Testcode verwendet, um Hamcrest-Matcher für eines meiner Geschäftsobjekte zu erstellen . Ich erinnere mich nicht an den genauen Code, aber er sah ungefähr so ​​aus (vereinfacht):

public class CustomerMatcher extends TypeSafeMatcher<Customer> {
    private final Matcher<? super String> nameMatcher;
    private final Matcher<? super LocalDate> birthdayMatcher;

    @Override
    protected boolean matchesSafely(Customer c) {
        return nameMatcher.matches(c.getName()) &&
               birthdayMatcher.matches(c.getBirthday());
    }

    private CustomerMatcher(Matcher<? super String> nameMatcher,
                            Matcher<? super LocalDate> birthdayMatcher) {
        this.nameMatcher = nameMatcher;
        this.birthdayMatcher = birthdayMatcher;
    }

    // builder methods from here on

    public static CustomerMatcher isCustomer() {
        // I could return a static instance here instead
        return new CustomerMatcher(Matchers.anything(), Matchers.anything());
    }

    public CustomerMatcher withBirthday(Matcher<? super LocalDate> birthdayMatcher) {
        return new CustomerMatcher(this.nameMatcher, birthdayMatcher);
    }

    public CustomerMatcher withName(Matcher<? super String> nameMatcher) {
        return new CustomerMatcher(nameMatcher, this.birthdayMatcher);
    }
}

Ich würde es dann in meinen Unit-Tests so verwenden (mit geeigneten statischen Importen):

assertThat(result, is(customer().withName(startsWith("Paŭlo"))));
Paŭlo Ebermann
quelle
1
Dies ist wahr - und ich denke, es ist ein sehr wichtiger Punkt, aber es löst nicht das letzte Problem. Es gibt Ihnen keine Möglichkeit, zu überprüfen, ob sich das Objekt beim Konstruieren in einem konsistenten Zustand befindet. Nach der Erstellung müssen Sie vor jeder Ihrer Geschäftsmethoden einen Validator aufrufen, um sicherzustellen, dass der Aufrufer alle Methoden festlegt (was eine schreckliche Praxis wäre). Ich denke, es ist gut, diese Art von Dingen zu durchdenken, um zu sehen, warum wir das Builder-Muster so verwenden, wie wir es tun.
Bill K
@BillK true - Ein Objekt mit im Wesentlichen unveränderlichen Setzern (die jedes Mal ein neues Objekt zurückgeben) bedeutet, dass jedes zurückgegebene Objekt gültig sein sollte. Wenn Sie ungültige Objekte zurückgeben (z. B. Person mit nameaber ohne age), funktioniert das Konzept nicht. Nun, Sie könnten tatsächlich so etwas zurückgeben, PartiallyBuiltPersonwährend es nicht gültig ist, aber es scheint ein Hack zu sein, um den Builder zu maskieren.
VLAZ
@BillK Das habe ich mit "Wenn es eine Möglichkeit gibt, ein erstes (gültiges / nützliches) Objekt zu erhalten" gemeint - Ich gehe davon aus, dass sowohl das ursprüngliche Objekt als auch das von jeder Builder-Funktion zurückgegebene Objekt gültig / konsistent sind. Als Ersteller einer solchen Klasse müssen Sie nur Builder-Methoden bereitstellen, die diese konsistenten Änderungen vornehmen.
Paŭlo Ebermann