¿Por qué el patrón de construcción a menudo se implementa así?

8

A menudo veo que la implementación del patrón de construcción (en Java) es así:

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
        }
    }
}

Ejemplos también aquí:

¿Por qué las personas introducen la fuerte dependencia (mutua) entre el constructor y el tipo para construir?

EDITAR: Quiero decir, sé que por su naturaleza el generador está vinculado a la clase desde la que quiero construir una instancia ( FooBuilder -> Foo). Lo que cuestiono es: ¿por qué existe la necesidad de lo contrario ( Foo -> FooBuilder)

Por el contrario, veo (con menos frecuencia) implementaciones que crean una nueva Fooinstancia de Foocuándo se crea el generador y establecen los campos en el caso de que se invoquen los métodos de generador apropiados. Me gusta más este enfoque, porque todavía tiene una API fluida y no vincula la clase a su generador. ¿Hay alguna desventaja de este otro enfoque?

Andy
fuente
¿Su pregunta también se aplica si el Fooconstructor es público?
Visto el
1
El motivo es SECO. Si tiene muchos campos, tendrían que repetirse en el constructor de Foo si Foo no conoce FooBuilder. De esta manera salvas al constructor de Foo.
Esben Skov Pedersen

Respuestas:

8

Se reduce a nunca tener un Foo en un estado medio construido

Algunas razones que me vienen a la mente:

El constructor está vinculado por naturaleza a la clase que está construyendo. Puede pensar en los estados intermedios del constructor como una aplicación parcial del constructor, por lo que su argumento de que está demasiado acoplado no es convincente.

Al pasar todo el generador, los campos en MyClass pueden ser finales, lo que facilita el razonamiento sobre MyClass. Si los campos no son definitivos, podría tener fácilmente acomodadores con fluidez que un constructor.

Caleth
fuente
1. por su naturaleza, el constructor está vinculado a la clase desde la que quiero construir una instancia (FooBuilder -> Foo). Lo que cuestiono es: ¿por qué existe la necesidad de lo contrario (Foo -> FooBuilder) 2. Veo el punto con el modificador final , pero además de ese modificador, el FooBuilder simplemente puede establecer los campos de Foo (incluso si son privados, porque FooBuilder es parte de Foo), y si no creo setters, la instancia sigue siendo inmutable, ¿no?
Andy
Se reduce a nunca tener un Foo en un estado medio construido, incluso si solo es interno para el constructor. ¿Creería que es un problema que en una clase con un gran conjunto de constructores de sobrecarga, haya una "fuerte (mutua) dependencia" entre los constructores y el resto de la clase? Porque eso es lo que es un constructor, una forma concisa de expresar muchos constructores similares
Caleth
Gracias por la respuesta (sobre nunca tener un estado inconsistente), eso lo deja claro. No me gusta la respuesta pero ahora la entiendo. Cuando incorpores eso en tu respuesta, lo aceptaré.
Andy
3

El constructor mantendrá campos mutables, establecidos con métodos. La clase real ( Foo) puede crear finalcampos inmutables desde el generador.

Entonces el Constructor es una clase con estado.

Pero el constructor también podría haber llamado new Foo(x, y, z). Eso sería más unidireccional, mejor estilo en mi humilde opinión. Como entonces, el antipatrón, un campo FooBuilder o similar no sería posible. Por otro lado, FooBuilder garantiza cierta integridad de los datos.

Por otro lado, FooBuilder desempeña dos funciones: para construir con una API fluida y para presentar datos al constructor. Es una clase de tipo evento para el constructor. En un código sobrediseñado, los parámetros del constructor podrían mantenerse en algunos FooConstructorParams.

Joop Eggen
fuente
2

Porque un "Constructor" se usa típicamente para resolver el "problema del constructor telescópico", especialmente en el contexto de clases inmutables. Vea este enlace para un ejemplo completo.

Considere cómo se verían las alternativas:

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

O:

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

Entonces, especialmente para las clases inmutables, si no quieres un constructor con muchos parámetros, en realidad no hay alternativa.

Doc Brown
fuente
Sé qué problema resuelve el patrón de construcción. Pero no respondió a mi pregunta por qué la implementación a menudo se parece al ejemplo dado. Incluso Josh Bloch en el artículo que vinculó no lo hace. Sin embargo, Caleth lo hace en su comentario.
Andy
@Andy: di un ejemplo para aclarar por qué esto responde a tu pregunta.
Doc Brown
En realidad, su segundo ejemplo no es necesariamente mutable. Si no implementa métodos setter, es inmutable . Pero ahora veo hacia dónde te diriges.
Andy
@Andy: podría reescribir el ejemplo usando setters si prefieres modismos de Java en lugar de modismos de C #. Pero si foo.Attribute1=par1es posible, Foodefinitivamente es mutable, incluso en Java. Las clases inmutables deben prohibir cualquier mutación de su estado después de la construcción, ni por los colocadores, ni por métodos que muten su estado, ni exponiendo los atributos de los miembros públicos.
Doc Brown
"Así que especialmente para las clases inmutables" .... Pero una clase sin setters seguirá apareciendo inmutable desde el exterior. Ya sea que sean finales o no, es algo agradable.
Esben Skov Pedersen