¿Mejoras en el patrón de diseño de constructor de Joshua Bloch?

12

En 2007, leí un artículo sobre Joshua Blochs sobre el "patrón de construcción" y cómo podría modificarse para mejorar el uso excesivo de constructores y setters, especialmente cuando un objeto tiene una gran cantidad de propiedades, la mayoría de las cuales son opcionales. Un breve resumen de este patrón de diseño se articula aquí .

Me gustó la idea y la he estado usando desde entonces. El problema es que, si bien es muy limpio y agradable de usar desde la perspectiva del cliente, ¡implementarlo puede ser un fastidio! Hay tantos lugares diferentes en el objeto donde una sola propiedad es referencia y, por lo tanto, crear el objeto y agregar una nueva propiedad lleva mucho tiempo.

Entonces ... tuve una idea. Primero, un objeto de ejemplo en el estilo de Joshua Bloch:

Estilo Josh Bloch:

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

Ahora mi versión "mejorada":

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

    }
}

Como puede ver en mi "versión mejorada", ¡hay 2 lugares menos en los que necesitamos agregar código sobre las propiedades de adición (u opciones, en este caso)! Lo único negativo que puedo ver es que las variables de instancia de la clase externa no pueden ser finales. Pero, la clase sigue siendo inmutable sin esto.

¿Existe realmente algún inconveniente en esta mejora en la mantenibilidad? Tiene que haber una razón por la que repitió las propiedades dentro de la clase anidada que no estoy viendo.

Jason Fotinatos
fuente
Esto parece muy similar a mi toma del patrón de construcción en C # aquí .
MattDavey

Respuestas:

12

Tu variación es bastante agradable. Pero permite a los usuarios hacer esto:

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!

Lo que más bien derrota al objeto.

Puede cambiar el buildmétodo para hacer esto:

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

Lo que evitaría esto: cualquier llamada a un método de establecimiento en el generador después de la buildllamada fallará con una NullPointerException. Si quisiera ser flash, incluso podría probar nulo y lanzar una IllegalStateException o algo así. Y podría mover eso a una clase base genérica donde podría usarse en todos los constructores.

Tom Anderson
fuente
1
Que cambiaría la segunda línea en build()que: this.options = new Options();. De esta forma, las instancias de Opciones serían inmutables de forma segura, además el generador sería reutilizable al mismo tiempo.
Natix
5

El constructor en el patrón de Bloch puede usarse muchas veces para generar objetos que son "mayormente" iguales. Además, los objetos inmutables (todos los campos son finales e inmutables) tienen ventajas de seguridad de subprocesos que sus cambios pueden vencer.

Steven Schlansker
fuente
0

Si Options era efectivamente clonable (quiero decir, independientemente de la interfaz Cloneable), podría usar un patrón prototipo: tener uno en el generador y clonarlo en build ().

Si no usa la interfaz Cloneable, tendría que copiar todos los campos, por lo que agregaría otro lugar donde necesita agregarlo, por lo que sería una buena idea al menos para la clase con campos simples que realmente usan Cloneable.

user470365
fuente