Warum wird das Builder-Muster oft so implementiert?


8

Oft sehe ich die Implementierung des Builder-Musters (in Java) folgendermaßen:

public class Foo {
    private Foo(FooBuilder builder) {
        // get alle the parameters from the builder and apply them to this instance
    }

    public static class FooBuilder {
        // ...
        public Foo build() {
            return new Foo(this); // <- this part is what irritates me
        }
    }
}

Beispiele auch hier:

Warum führen Menschen die starke (gegenseitige) Abhängigkeit zwischen Builder und Typ ein, um zu bauen?

EDIT: Ich meine, ich weiß, dass der Builder naturgemäß an die Klasse gebunden ist, aus der ich eine Instanz erstellen möchte ( FooBuilder -> Foo). Was ich frage ist: Warum gibt es die Notwendigkeit für den umgekehrten Fall ( Foo -> FooBuilder)

Im Gegenteil, ich sehe (seltener) Implementierungen, die einen neuen Zeitpunkt für Foodie Erstellung des Builders erstellen und die Felder für die Instanz festlegen, in der Foodie entsprechenden Builder-Methoden aufgerufen werden. Ich mag diesen Ansatz besser, weil er immer noch eine fließende API hat und die Klasse nicht an ihren Builder bindet. Gibt es einen Nachteil dieses anderen Ansatzes?


Gilt Ihre Frage auch, wenn der FooKonstruktor öffentlich ist?
Spotted

1
Der Grund ist trocken. Wenn Sie viele Felder haben, müssen diese im Konstruktor von Foo wiederholt werden, wenn Foo nichts über FooBuilder weiß. Auf diese Weise speichern Sie den Konstruktor von Foo.
Esben Skov Pedersen

Antworten:


8

Es läuft darauf hinaus, niemals einen Foo in einem halb konstruierten Zustand zu haben

Einige Gründe, die mir in den Sinn kommen:

Der Builder ist von Natur aus an die Klasse gebunden, die er konstruiert. Sie können sich die Zwischenzustände des Builders als Teilanwendung des Konstruktors vorstellen, sodass Ihr Argument, dass er zu gekoppelt ist, nicht überzeugend ist.

Durch die Übergabe des gesamten Builders können Felder in MyClass endgültig sein, was das Nachdenken über MyClass erleichtert. Wenn die Felder nicht endgültig sind, können Sie genauso gut fließende Setter haben wie ein Builder.


1. Der Builder ist naturgemäß an die Klasse gebunden, aus der ich eine Instanz erstellen möchte (FooBuilder -> Foo). Was ich frage ist: Warum gibt es die Notwendigkeit für den umgekehrten Fall (Foo -> FooBuilder) 2. Ich sehe den Punkt mit dem letzten Modifikator, aber neben diesem Modifikator kann der FooBuilder einfach die Felder von Foo setzen (auch wenn sie es sind) sind privat, weil FooBuilder Teil von Foo ist), und wenn ich keine Setter erstelle, ist die Instanz immer noch unveränderlich, nicht wahr?
Andy

Es läuft darauf hinaus, niemals ein Foo in einem halb konstruierten Zustand zu haben, selbst wenn es immer nur innerhalb des Erbauers liegt. Würden Sie es für ein Problem halten, dass in einer Klasse mit einer großen Anzahl von Konstruktoren eine "starke (gegenseitige) Abhängigkeit" zwischen den Konstruktoren und dem Rest der Klasse bestand? Denn
genau

Vielen Dank für die Antwort (dass Sie nie einen inkonsistenten Zustand haben), die es klar macht. Ich mag die Antwort nicht, aber ich verstehe sie jetzt. Wenn Sie das in Ihre Antwort aufnehmen, werde ich es akzeptieren.
Andy

3

Der Builder enthält veränderbare Felder, die mit Methoden festgelegt wurden. Die tatsächliche Klasse ( Foo) kann dann unveränderliche finalFelder aus dem Builder erstellen .

Der Builder ist also eine Stateful-Klasse.

Aber der Baumeister hätte auch anrufen können new Foo(x, y, z). Das wäre meiner Meinung nach eher einseitig und besser. Da dann das Anti-Pattern, ein FooBuilder-Feld oder so nicht möglich wäre. Andererseits garantiert der FooBuilder eine gewisse Integrität der Daten.

Auf der anderen Seite spielt FooBuilder zwei Rollen: zum Erstellen mit einer fließenden API und zum Präsentieren von Daten für den Konstruktor. Es ist eine Ereignistypklasse für den Konstruktor. In einem überentwickelten Code könnten die Konstruktorparameter in einigen FooConstructorParams beibehalten werden.


2

Weil ein "Builder" normalerweise verwendet wird, um das "Teleskopkonstruktorproblem" zu lösen, insbesondere im Zusammenhang mit unveränderlichen Klassen. Unter diesem Link finden Sie ein umfassendes Beispiel.

Überlegen Sie, wie die Alternativen aussehen würden:

 public static class FooBuilder {
    // works with immutable classes, but leads to telescoping constructor
    public Foo build() {
        return new Foo(par1, par2, par3, ....); 
    }
 }

Oder:

 public static class FooBuilder {
    // does not work for immutable classes:
    public Foo build() {
        var foo = new Foo(); 
        foo.Attribute1=par1;
        foo.Attribute2=par2;
        //...
        return foo;
    }
 }

Insbesondere für unveränderliche Klassen gibt es keine Alternative, wenn Sie keinen Konstruktor mit einer Reihe von Parametern möchten.


Ich weiß, welches Problem das Builder-Muster löst. Aber Sie haben meine Frage nicht beantwortet, warum die Implementierung oft wie im angegebenen Beispiel aussieht. Sogar Josh Bloch in dem Artikel, den Sie verlinkt haben, tut dies nicht. Caleth tut dies jedoch in seinem / ihrem Kommentar.
Andy

@Andy: Ich habe ein Beispiel gegeben, um zu verdeutlichen, warum dies Ihre Frage beantwortet.
Doc Brown

Eigentlich ist Ihr zweites Beispiel nicht unbedingt veränderlich. Wenn Sie keine Setter-Methoden implementieren, ist dies unveränderlich . Aber ich sehe jetzt, wohin Sie gehen.
Andy

@Andy: Ich könnte das Beispiel tatsächlich mit Setzern umschreiben, wenn Sie Java-Idiome anstelle von C # -Idiomen bevorzugen. Aber wenn foo.Attribute1=par1es möglich ist, Fooist es definitiv veränderlich, auch in Java. Unveränderliche Klassen müssen jegliche Mutation ihres Zustands nach der Konstruktion verbieten, weder durch Setter noch durch Methoden, die ihren Zustand mutieren, noch durch Aufdecken von Attributen öffentlicher Mitglieder.
Doc Brown

"Also besonders für unveränderliche Klassen" .... Aber eine Klasse ohne Setter wird von außen immer noch unveränderlich erscheinen. Ob sie endgültig sind oder nicht, ist eine schöne Sache.
Esben Skov Pedersen
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.