Miglioramenti al modello di progettazione del costruttore di Joshua Bloch?


12

Nel 2007, ho letto un articolo su Joshua Blochs che riprende il "modello di costruttore" e come potrebbe essere modificato per migliorare l'uso eccessivo di costruttori e setter, specialmente quando un oggetto ha un gran numero di proprietà, molte delle quali sono opzionali. Un breve riassunto di questo modello di progettazione è articolato qui .

Mi è piaciuta l'idea e la uso da allora. Il problema è che, sebbene sia molto pulito e piacevole da usare dal punto di vista del cliente, implementarlo può essere una seccatura! Ci sono così tanti posti diversi nell'oggetto in cui una singola proprietà è di riferimento e quindi la creazione dell'oggetto e l'aggiunta di una nuova proprietà richiede molto tempo.

Quindi ... ho avuto un'idea. Innanzitutto, un oggetto di esempio nello stile di 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();
    }
}

Ora la mia versione "migliorata":

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();

    }
}

Come puoi vedere nella mia "versione migliorata", ci sono 2 posti in meno in cui è necessario aggiungere codice su eventuali proprietà di aggiunta (o opzioni, in questo caso)! L'unico aspetto negativo che posso vedere è che le variabili di istanza della classe esterna non sono in grado di essere definitive. Ma la classe è ancora immutabile senza questo.

C'è davvero un aspetto negativo di questo miglioramento della manutenibilità? Ci deve essere un motivo per cui ha ripetuto le proprietà all'interno della classe nidificata che non vedo?


Questo sembra molto simile alla mia interpretazione del modello di generatore in C # qui .
MattDavey,

Risposte:


12

La tua variazione è abbastanza bella. Ma consente agli utenti di fare questo:

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!

Che piuttosto sconfigge l'oggetto.

È possibile modificare il buildmetodo per fare ciò:

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

Il che impedirebbe questo: qualsiasi chiamata a un metodo setter sul builder dopo la buildchiamata fallirà con una NullPointerException. Se vuoi essere flash, potresti anche provare a null e lanciare un IllegalStateException o qualcosa del genere. E potresti spostarlo fino a una classe base generica in cui potrebbe essere utilizzato in tutti i costruttori.


1
Vorrei cambiare la linea 2 in build()a: this.options = new Options();. In questo modo, le istanze di Opzioni sarebbero immutabili in modo sicuro, inoltre il builder sarebbe riutilizzabile allo stesso tempo.
Natix,

5

Il costruttore nel modello di Bloch può essere usato molte volte per generare oggetti "per lo più" uguali. Inoltre, gli oggetti immutabili (tutti i campi sono definitivi e essi stessi immutabili) presentano vantaggi in termini di sicurezza del thread che le modifiche potrebbero essere annullate.


0

Se Opzioni fosse effettivamente clonabile (intendo, indipendentemente dall'interfaccia Cloneable), potresti usare un modello prototipo: ne hai uno nel generatore e clonalo in build ().

Se non usi l'interfaccia di Cloneable, dovresti copiare ogni campo, quindi aggiungerebbe un altro posto in cui devi aggiungerlo, quindi almeno per la classe con campi semplici in realtà usando Cloneable sarebbe una buona idea.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.