Melhorias no Builder Design Pattern de Joshua Bloch?

12

Em 2007, li um artigo sobre Joshua Blochs sobre o "padrão de construtor" e como ele poderia ser modificado para melhorar o uso excessivo de construtores e setters, especialmente quando um objeto tem um grande número de propriedades, a maioria das quais é opcional. Um breve resumo desse padrão de design está articulado aqui .

Gostei da ideia e uso-a desde então. O problema é que, embora seja muito limpo e agradável de usar da perspectiva do cliente, implementá-lo pode ser uma chatice! Existem muitos lugares diferentes no objeto em que uma única propriedade é referência e, assim, criar o objeto e adicionar uma nova propriedade leva muito tempo.

Então ... eu tive uma ideia. Primeiro, um objeto de exemplo no estilo de Joshua Bloch:

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

Agora minha versão "aprimorada":

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 você pode ver na minha "versão aprimorada", há menos 2 lugares nos quais precisamos adicionar código sobre quaisquer propriedades adicionais (ou opções, neste caso)! O único aspecto negativo que vejo é que as variáveis ​​de instância da classe externa não podem ser finais. Mas, a classe ainda é imutável sem isso.

Existe realmente alguma desvantagem nessa melhoria da capacidade de manutenção? Tem que haver uma razão pela qual ele repetiu as propriedades dentro da classe aninhada que eu não estou vendo?

Jason Fotinatos
fonte
Isso parece muito semelhante ao meu exame do padrão do construtor em C # aqui .
precisa saber é o seguinte

Respostas:

12

Sua variação é bastante agradável. Mas permite que os usuários façam isso:

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!

O que derrota o objeto.

Você pode alterar o buildmétodo para fazer isso:

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

O que impediria isso - qualquer chamada para um método setter no construtor após a buildchamada falhará com um NullPointerException. Se você quisesse ser flash, poderia até testar nulo e lançar uma IllegalStateException ou algo assim. E você pode mover isso para uma classe base genérica, onde ela pode ser usada em todos os construtores.

Tom Anderson
fonte
1
Eu mudaria a 2ª linha em build()que: this.options = new Options();. Dessa forma, as instâncias de Opções seriam imutáveis ​​com segurança, e o construtor seria reutilizável ao mesmo tempo.
Natix 04/04
5

O construtor no padrão de Bloch pode ser usado várias vezes para gerar objetos que são "basicamente" iguais. Além disso, objetos imutáveis ​​(todos os campos são finais e são imutáveis) possuem vantagens de segurança de encadeamento que suas alterações podem prejudicar.

Steven Schlansker
fonte
0

Se o Options fosse efetivamente clonável (quero dizer, independentemente da interface Cloneable), você poderia usar o padrão de protótipo - ter um no construtor e cloná-lo no build ().

Se você não usa a interface Cloneable, é necessário copiar todos os campos, para adicionar outro local onde é necessário adicioná-lo; portanto, pelo menos para a classe com campos simples que usam Cloneable, seria uma boa idéia.

user470365
fonte