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.