Pergunta

Motivation

Recentemente eu procurava uma maneira de inicializar um objeto complexo sem passar um monte de parâmetro para o construtor. Eu tentei com o padrão do construtor, mas eu não gosto do fato, que eu não sou capaz de verificar em tempo de compilação se eu realmente definir todos os valores necessários.

padrão do construtor tradicional

Quando eu uso o padrão do construtor para criar meu objeto Complex, a criação é mais "typesafe", porque é mais fácil de ver o que um argumento é utilizado para:

new ComplexBuilder()
        .setFirst( "first" )
        .setSecond( "second" )
        .setThird( "third" )
        ...
        .build();

Mas agora eu tenho o problema, que eu posso facilmente perder um parâmetro importante. I pode verificar-lo dentro do método build(), mas isso é apenas em tempo de execução. Em tempo de compilação não há nada que me avisa, se eu perdi alguma coisa.

avançado padrão do construtor

Agora, a minha ideia era criar um construtor, que "lembra" me se eu perdi um parâmetro necessário. Minha primeira tentativa esta aparência:

public class Complex {
    private String m_first;
    private String m_second;
    private String m_third;

    private Complex() {}

    public static class ComplexBuilder {
        private Complex m_complex;

        public ComplexBuilder() {
            m_complex = new Complex();
        }

        public Builder2 setFirst( String first ) {
            m_complex.m_first = first;
            return new Builder2();
        }

        public class Builder2 {
            private Builder2() {}
            Builder3 setSecond( String second ) {
                m_complex.m_second = second;
                return new Builder3();
            }
        }

        public class Builder3 {
            private Builder3() {}
            Builder4 setThird( String third ) {
                m_complex.m_third = third;
                return new Builder4();
            }
        }

        public class Builder4 {
            private Builder4() {}
            Complex build() {
                return m_complex;
            }
        }
    }
}

Como você pode ver, cada setter da classe construtor retorna uma classe construtor interno diferente. Cada classe construtor interna proporciona exatamente um método setter eo último fornece apenas um build () método.

Agora, a construção de um objeto de novo se parece com isso:

new ComplexBuilder()
    .setFirst( "first" )
    .setSecond( "second" )
    .setThird( "third" )
    .build();

... mas não há maneira de esquecer um parâmetro necessário. O compilador não iria aceitá-lo.

Opcional parâmetros

Se eu tivesse parâmetros opcionais, eu usaria o último Builder4 classe construtor interna para defini-los como um construtor "tradicional" faz, voltando-se.

Perguntas

  • Este é um padrão bem conhecido? Será que ela tem um nome especial?
  • Você vê qualquer armadilhas?
  • Você tem idéias para melhorar a execução -? No sentido de menos linhas de código
Foi útil?

Solução

Não, não é novo. O que você está fazendo realmente existe criando uma espécie de um DSL estendendo o construtor padrão padrão para ramos de apoio que está entre outras coisas, uma excelente maneira de garantir que o construtor não produz um conjunto de conflito configurações para o objeto real.

Pessoalmente acho que esta é uma grande extensão ao padrão do construtor e você pode fazer todo tipo de coisas interessantes com ele, por exemplo, no trabalho temos construtores DSL para alguns de nossos testes de integridade de dados que nos permitem fazer coisas como assertMachine().usesElectricity().and().makesGrindingNoises().whenTurnedOn();. OK, talvez não o melhor exemplo possível, mas eu acho que você começa o ponto.

Outras dicas

O padrão do construtor tradicional já lida com isso: simplesmente tomar os parâmetros obrigatórios no construtor. Claro, nada impede que um chamador de passar null, mas também não faz o seu método.

O grande problema que vejo com seu método é que você quer ter uma explosão combinatorical das classes com o número de parâmetros obrigatórios, ou forçar o usuário a definir os parâmetros em um sqeuence particular, o que é irritante.

Além disso, é um monte de trabalho adicional.

public class Complex {
    private final String first;
    private final String second;
    private final String third;

    public static class False {}
    public static class True {}

    public static class Builder<Has1,Has2,Has3> {
        private String first;
        private String second;
        private String third;

        private Builder() {}

        public static Builder<False,False,False> create() {
            return new Builder<>();
        }

        public Builder<True,Has2,Has3> setFirst(String first) {
            this.first = first;
            return (Builder<True,Has2,Has3>)this;
        }

        public Builder<Has1,True,Has3> setSecond(String second) {
            this.second = second;
            return (Builder<Has1,True,Has3>)this;
        }

        public Builder<Has1,Has2,True> setThird(String third) {
            this.third = third;
            return (Builder<Has1,Has2,True>)this;
        }
    }

    public Complex(Builder<True,True,True> builder) {
        first = builder.first;
        second = builder.second;
        third = builder.third;
    }

    public static void test() {
        // Compile Error!
        Complex c1 = new Complex(Complex.Builder.create().setFirst("1").setSecond("2"));

        // Compile Error!
        Complex c2 = new Complex(Complex.Builder.create().setFirst("1").setThird("3"));

        // Works!, all params supplied.
        Complex c3 = new Complex(Complex.Builder.create().setFirst("1").setSecond("2").setThird("3"));
    }
}

Por que você não colocar "necessários" parâmetros no construtor construtores?

public class Complex
{
....
  public static class ComplexBuilder
  {
     // Required parameters
     private final int required;

     // Optional parameters
     private int optional = 0;

     public ComplexBuilder( int required )
     {
        this.required = required;
     } 

     public Builder setOptional(int optional)
     {
        this.optional = optional;
     }
  }
...
}

Este padrão é descrito no Effective Java .

Em vez de usar várias classes I iria usar apenas uma classe e várias interfaces. Ele reforça sua sintaxe sem a necessidade tanto de digitação. Ele também permite que você veja todo o código relacionado próximo conjunto que faz com que seja mais fácil de entender o que está acontecendo com o seu código em um nível maior.

IMHO, isso parece inchado. Se você Have para ter todos os parâmetros, passá-los no construtor.

Eu vi / usei esse:

new ComplexBuilder(requiredvarA, requiedVarB).optional(foo).optional(bar).build();

Em seguida, passá-los para o seu objeto que lhes exige.

O padrão Builder é geralmente usado quando você tem um monte de parâmetros opcionais. Se você achar que precisa de muitos parâmetros necessários, considere estas opções em primeiro lugar:

  • Sua classe poderia estar fazendo muito. verifique que ela não viola responsabilidade única Princípio . Pergunte-se porque você precisa de uma classe com tantas variáveis ??de instância necessárias.
  • Você construtor pode ser fazendo muito . O trabalho de um construtor é construir. (Eles não ficar muito criativo quando nomeou-o; D) Assim como as classes, os métodos têm uma responsabilidade única Princípio. Se o seu construtor está fazendo mais de atribuição de campo apenas, você precisa de um bom motivo para justificar isso. Você pode achar que você precisa de um Factory Method em vez de um construtor.
  • Seus parâmetros podem ser fazendo muito pouco . Pergunte a si mesmo se os seus parâmetros podem ser agrupados em um pequeno struct (ou struct-como objeto, no caso de Java). Não tenha medo de fazer pequenas classes. Se você achar que você precisa para fazer um struct ou classe pequena, não se esqueça para refactor out funcionalidade que pertence ao struct em vez de sua classe maior.

Para mais informações sobre quando usar o padrão Builder e suas vantagens você deve verificar o meu post para outra pergunta semelhante aqui

Pergunta 1: Em relação ao nome do padrão, eu como o nome "Passo Builder":

Pergunta 2/3: Em relação armadilhas e recomendações, este se sente mais complicado para a maioria das situações.

  • Você está impondo um seqüência na forma como você usa seu construtor que é incomum em minha experiência. Eu podia ver como isso seria importante em alguns casos, mas eu nunca precisei. Por exemplo, eu não vejo a necessidade de forçar uma seqüência aqui:

    Person.builder().firstName("John").lastName("Doe").build() Person.builder().lastName("Doe").firstName("John").build()

  • No entanto, muitas vezes o construtor necessário para impor algumas restrições para evitar que objetos falsos de ser construído. Talvez você quer garantir que todos os campos são fornecidos ou que combinações de campos são válidos. Eu estou supondo que esta é a verdadeira razão você quer introduzir sequenciamento para dentro do prédio.

    Neste caso, eu como recomendação de Joshua Bloch para fazer a validação no método build (). Isto ajuda com a validação de campo cruz porque tudo está disponível neste momento. Veja esta resposta: https://softwareengineering.stackexchange.com/a/241320

Em resumo, eu não adicionar qualquer complicação com o código só porque você está preocupado com a "falta" uma chamada para um método construtor. Na prática, isso é facilmente pego com um caso de teste. Talvez começar com um construtor de baunilha e, em seguida, introduzir este se você manter mordido por chamadas de método faltando.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top