Verbesserungen am Builder-Entwurfsmuster von Joshua Bloch?

12

Im Jahr 2007 las ich einen Artikel über Joshua Blochs "Builder-Pattern" und wie es modifiziert werden könnte, um die Übernutzung von Konstruktoren und Setzern zu verbessern, insbesondere wenn ein Objekt eine große Anzahl von Eigenschaften hat, von denen die meisten optional sind. Eine kurze Zusammenfassung dieses Entwurfsmusters finden Sie hier .

Ich mochte die Idee und benutze sie seitdem. Das Problem dabei ist, dass es aus Kundensicht sehr sauber und schön zu bedienen ist. Die Implementierung kann jedoch nerven! Es gibt so viele verschiedene Stellen im Objekt, an denen eine einzelne Eigenschaft referenziert wird. Das Erstellen des Objekts und das Hinzufügen einer neuen Eigenschaft nimmt daher viel Zeit in Anspruch.

Also ... ich hatte eine Idee. Zunächst ein Beispielobjekt im Stil von Joshua Bloch:

Josh Bloch Style:

public class OptionsJoshBlochStyle {

    private final String option1;
    private final int option2;
    // ...other options here  <<<<

    public String getOption1() {
        return option1;
    }

    public int getOption2() {
        return option2;
    }

    public static class Builder {

        private String option1;
        private int option2;
        // other options here <<<<<

        public Builder option1(String option1) {
            this.option1 = option1;
            return this;
        }

        public Builder option2(int option2) {
            this.option2 = option2;
            return this;
        }

        public OptionsJoshBlochStyle build() {
            return new OptionsJoshBlochStyle(this);
        }
    }

    private OptionsJoshBlochStyle(Builder builder) {
        this.option1 = builder.option1;
        this.option2 = builder.option2;
        // other options here <<<<<<
    }

    public static void main(String[] args) {
        OptionsJoshBlochStyle optionsVariation1 = new OptionsJoshBlochStyle.Builder().option1("firefox").option2(1).build();
        OptionsJoshBlochStyle optionsVariation2 = new OptionsJoshBlochStyle.Builder().option1("chrome").option2(2).build();
    }
}

Nun meine "verbesserte" Version:

public class Options {

    // note that these are not final
    private String option1;
    private int option2;
    // ...other options here

    public String getOption1() {
        return option1;
    }

    public int getOption2() {
        return option2;
    }

    public static class Builder {

        private final Options options = new Options();

        public Builder option1(String option1) {
            this.options.option1 = option1;
            return this;
        }

        public Builder option2(int option2) {
            this.options.option2 = option2;
            return this;
        }

        public Options build() {
            return options;
        }
    }

    private Options() {
    }

    public static void main(String[] args) {
        Options optionsVariation1 = new Options.Builder().option1("firefox").option2(1).build();
        Options optionsVariation2 = new Options.Builder().option1("chrome").option2(2).build();

    }
}

Wie Sie in meiner "verbesserten Version" sehen können, gibt es zwei Stellen weniger, an denen wir Code über zusätzliche Eigenschaften (oder Optionen in diesem Fall) hinzufügen müssen! Das einzige Negative, das ich sehen kann, ist, dass die Instanzvariablen der äußeren Klasse nicht endgültig sein können. Aber die Klasse ist ohne dies immer noch unveränderlich.

Gibt es wirklich Nachteile bei dieser Verbesserung der Wartbarkeit? Muss es einen Grund geben, warum er die Eigenschaften innerhalb der verschachtelten Klasse wiederholt, die ich nicht sehe?

Jason Fotinatos
quelle
Dies scheint meiner Ansicht über das Builder-Muster in C # hier sehr ähnlich zu sein .
MattDavey

Antworten:

12

Deine Variante ist ganz nett. Aber damit können Benutzer Folgendes tun:

Options.Builder builder = new Options.Builder().option1("firefox").option2(1);
Options optionsVariation1 = builder.build();
assert optionsVariation1.getOption1().equals("firefox");
builder.option1("chrome");
assert optionsVariation1.getOption1().equals("firefox"); // FAILURE!

Was das Objekt eher besiegt.

Sie können die buildMethode ändern , um dies zu tun:

public Options build() {
    Options options = this.options;
    this.options = null;
    return options;
}

Dies würde dies verhindern. Jeder Aufruf einer Setter-Methode im Builder nach dem buildAufruf schlägt mit einer NullPointerException fehl. Wenn Sie Flash sein möchten, können Sie sogar auf null testen und stattdessen eine IllegalStateException oder etwas anderes auslösen. Und Sie können dies auf eine generische Basisklasse verschieben, in der es für alle Builder verwendet werden kann.

Tom Anderson
quelle
1
Ich würde die 2. Zeile ändern in build()zu: this.options = new Options();. Auf diese Weise sind die Options-Instanzen sicher unveränderlich, und der Builder kann gleichzeitig wiederverwendet werden.
Natix
5

Der Builder in Blochs Muster kann viele Male verwendet werden, um Objekte zu generieren, die "größtenteils" gleich sind. Darüber hinaus haben unveränderliche Objekte (alle Felder sind endgültig und selbst unveränderlich) Thread-Sicherheitsvorteile, die Ihre Änderungen möglicherweise zunichte machen.

Steven Schlansker
quelle
0

Wenn Options effektiv klonbar wäre (ich meine, unabhängig von der klonbaren Schnittstelle), könnten Sie ein Prototypmuster verwenden - haben Sie eines im Builder und klonen Sie es in build ().

Wenn Sie die Cloneable-Schnittstelle nicht verwenden, müssen Sie jedes Feld kopieren, damit es an einer anderen Stelle hinzugefügt wird, an der Sie es hinzufügen müssen. Zumindest für Klassen mit einfachen Feldern ist die Verwendung von Cloneable eine gute Idee.

user470365
quelle