Por que o padrão do construtor geralmente é implementado assim?


8

Muitas vezes, vejo a implementação do padrão do construtor (em Java) assim:

public class Foo {
    private Foo(FooBuilder builder) {
        // get alle the parameters from the builder and apply them to this instance
    }

    public static class FooBuilder {
        // ...
        public Foo build() {
            return new Foo(this); // <- this part is what irritates me
        }
    }
}

Exemplos também aqui:

Por que as pessoas introduzem a forte dependência (mútua) entre construtor e tipo a construir?

EDIT: Quero dizer, eu sei que, por sua natureza, o construtor está vinculado à classe da qual quero criar uma instância a partir de ( FooBuilder -> Foo). O que eu questiono é: por que existe a necessidade de o contrário ( Foo -> FooBuilder)

Pelo contrário, vejo (com menos frequência) implementações que criam uma nova instância de Fooquando o construtor é criado e configuram os campos na instância de Fooquando os métodos de construtor apropriados são chamados. Eu gosto mais dessa abordagem, porque ela ainda possui uma API fluente e não vincula a classe ao seu construtor. Existe alguma desvantagem dessa outra abordagem?


Sua pergunta também se aplica se o Fooconstrutor for público?
manchado

1
O motivo é SECO. Se você tiver muitos campos, eles deverão ser repetidos no construtor do Foo se o Foo não souber sobre o FooBuilder. Dessa forma, você salva o construtor do Foo.
Esben Skov Pedersen

Respostas:


8

Tudo se resume a nunca ter um Foo em um estado meio construído

Alguns motivos que vêm à mente:

O construtor está por natureza vinculado à classe que está construindo. Você pode pensar nos estados intermediários do construtor como aplicação parcial do construtor, portanto, seu argumento de que ele é muito acoplado não é convincente.

Ao passar o construtor inteiro para dentro, os campos no MyClass podem ser finais, facilitando o raciocínio sobre o MyClass. Se os campos não forem finais, você poderia facilmente ter setters fluentes do que um construtor.


1. por natureza, o construtor está vinculado à classe da qual desejo criar uma instância (FooBuilder -> Foo). O que estou questionando é: por que existe a necessidade do contrário (Foo -> FooBuilder) 2. Eu vejo o ponto com o modificador final , mas além desse modificador, o FooBuilder pode simplesmente definir os campos de Foo (mesmo que eles são particulares, porque o FooBuilder faz parte do Foo) e, se eu não criar setters, a instância ainda é imutável, não é?
Andy

Tudo se resume a nunca ter um Foo em um estado semi-construído, mesmo que seja apenas interno ao construtor. Você acha que é um problema que, em uma classe com um grande conjunto de construtores de sobrecarga, haja uma "forte dependência (mútua)" entre os construtores e o restante da classe? Porque é isso que um construtor é - uma maneira concisa de expressar lotes de construtores semelhantes
Caleth

Obrigado pela resposta (sobre nunca ter um estado inconsistente), que deixa claro. Não gosto da resposta, mas entendo agora. Quando você incorporar isso na sua resposta, eu a aceitarei.
Andy Andy

3

O construtor manterá campos mutáveis, configurados com métodos. A classe real ( Foo) pode criar finalcampos imutáveis ​​a partir do construtor.

Portanto, o Builder é uma classe com estado.

Mas o construtor também poderia ter chamado new Foo(x, y, z). Isso seria mais IMHO unidirecional e melhor. Como então o anti-padrão, um campo FooBuilder ou outro não seria possível. Por outro lado, o FooBuilder garante alguma integridade dos dados.

Por outro lado, o FooBuilder desempenha duas funções: criar com uma API fluente e apresentar dados ao construtor. É uma classe de tipo de evento para o construtor. Em um código com excesso de design, os parâmetros do construtor podem ser mantidos em alguns FooConstructorParams.


2

Como um "Construtor" é normalmente usado para resolver o "problema do construtor telescópico", especialmente no contexto de classes imutáveis. Veja este link para um exemplo completo.

Considere como seriam as alternativas:

 public static class FooBuilder {
    // works with immutable classes, but leads to telescoping constructor
    public Foo build() {
        return new Foo(par1, par2, par3, ....); 
    }
 }

Ou:

 public static class FooBuilder {
    // does not work for immutable classes:
    public Foo build() {
        var foo = new Foo(); 
        foo.Attribute1=par1;
        foo.Attribute2=par2;
        //...
        return foo;
    }
 }

Portanto, especialmente para classes imutáveis, se você não deseja um construtor com vários parâmetros, na verdade não há alternativa.


Eu sei qual problema o padrão do construtor resolve. Mas você não respondeu à minha pergunta por que a implementação geralmente se parece com o exemplo dado. Mesmo Josh Bloch no artigo que você vinculou não. No entanto, Caleth faz em seu comentário.
Andy Andy

@ Andy: Dei um exemplo para esclarecer por que isso responde à sua pergunta.
Doc Brown

Na verdade, seu segundo exemplo não é necessariamente mutável. Se você não implementar métodos de setter, é imutável . Mas agora vejo onde você está indo.
Andy Andy

@ Andy: Eu poderia realmente reescrever o exemplo usando setters, se você preferir idiomas Java em vez de idiomas C #. Mas, se foo.Attribute1=par1possível, Fooé definitivamente mutável, mesmo em Java. Classes imutáveis ​​precisam proibir qualquer mutação de seu estado após a construção, nem por levantadores, nem por métodos que mutam seu estado, nem expondo atributos de membros públicos.
Doc Brown

"Especialmente para as classes imutáveis" .... Mas uma classe sem levantadores ainda parecerá imutável do lado de fora. Se eles são finais ou não, é bom ter.
Esben Skov Pedersen
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.