Saltar al contenido

Patrón de constructor y herencia

Necesitamos tu apoyo para compartir nuestros escritos referente a las ciencias informáticas.

Solución:

Esto ciertamente es posible con el enlace recursivo, pero los constructores de subtipos también deben ser genéricos y se necesitan algunas clases abstractas provisionales. Es un poco engorroso, pero sigue siendo más fácil que la versión no genérica.

/**
 * Extend this for Mammal subtype builders.
 */
abstract class GenericMammalBuilder> 
    String sex;
    String name;

    B sex(String sex) 
        this.sex = sex;
        return self();
    

    B name(String name) 
        this.name = name;
        return self();
    

    abstract Mammal build();

    @SuppressWarnings("unchecked")
    final B self() 
        return (B) this;
    


/**
 * Use this to actually build new Mammal instances.
 */
final class MammalBuilder extends GenericMammalBuilder 
    @Override
    Mammal build() 
        return new Mammal(this);
    


/**
 * Extend this for Rabbit subtype builders, e.g. LopBuilder.
 */
abstract class GenericRabbitBuilder>
        extends GenericMammalBuilder 
    Color furColor;

    B furColor(Color furColor) 
        this.furColor = furColor;
        return self();
    

    @Override
    abstract Rabbit build();


/**
 * Use this to actually build new Rabbit instances.
 */
final class RabbitBuilder extends GenericRabbitBuilder 
    @Override
    Rabbit build() 
        return new Rabbit(this);
    

Hay una manera de evitar tener las clases hoja “concretas”, donde si tuviéramos esto:

class MammalBuilder> 
    ...

class RabbitBuilder>
        extends MammalBuilder 
    ...

Luego, debe crear nuevas instancias con un diamante y usar comodines en el tipo de referencia:

static RabbitBuilder builder() 
    return new RabbitBuilder<>();

Eso funciona porque el límite en la variable de tipo asegura que todos los métodos de, por ejemplo, RabbitBuilder tener un tipo de retorno con RabbitBuilder, incluso cuando el argumento de tipo es solo un comodín.

Sin embargo, no soy muy fan de eso, porque necesitas usar comodines en todas partes, y solo puedes crear una nueva instancia usando el diamante o un tipo crudo. Supongo que terminas con un poco de incomodidad de cualquier manera.


Y por cierto, sobre esto:

@SuppressWarnings("unchecked")
final B self() 
    return (B) this;

Hay una forma de evitar ese elenco sin marcar, que es hacer que el método sea abstracto:

abstract B self();

Y luego anótelo en la subclase de hoja:

@Override
RabbitBuilder self()  return this; 

El problema de hacerlo de esa manera es que, aunque es más seguro para los tipos, la subclase puede devolver algo diferente a this. Básicamente, de cualquier manera, la subclase tiene la oportunidad de hacer algo mal, por lo que realmente no veo muchas razones para preferir uno de esos enfoques sobre el otro.

Si alguien todavía se encuentra con el mismo problema, sugiero la siguiente solución, que se ajusta al patrón de diseño “Preferir composición sobre herencia”.

Clase para padres

El elemento principal es la interfaz que el constructor de la clase padre debe implementar:

public interface RabbitBuilder 
    public T sex(String sex);
    public T name(String name);

Aquí está la clase principal modificada con el cambio:

public class Rabbit 
    public String sex;
    public String name;

    public Rabbit(Builder builder) 
        sex = builder.sex;
        name = builder.name;
    

    public static class Builder implements RabbitBuilder 
        protected String sex;
        protected String name;

        public Builder() 

        public Rabbit build() 
            return new Rabbit(this);
        

        @Override
        public Builder sex(String sex) 
            this.sex = sex;
            return this;
        

        @Override
        public Builder name(String name) 
            this.name = name;
            return this;
        
    

La clase infantil

La clase infantil Builder debe implementar la misma interfaz (con diferente tipo genérico):

public static class LopBuilder implements RabbitBuilder

Dentro de la clase infantil Builder el campo que hace referencia al padreBuilder:

private Rabbit.Builder baseBuilder;

esto asegura que el padre Builder Los métodos se llaman en el niño, sin embargo, su implementación es diferente:

@Override
public LopBuilder sex(String sex) 
    baseBuilder.sex(sex);
    return this;


@Override
public LopBuilder name(String name) 
    baseBuilder.name(name);
    return this;


public Rabbit build() 
    return new Lop(this);

El constructor de Builder:

public LopBuilder() 
    baseBuilder = new Rabbit.Builder();

El constructor de la clase secundaria compilada:

public Lop(LopBuilder builder) 
    super(builder.baseBuilder);

Enfrentado con el mismo problema, utilicé la solución propuesta por emcmanus en: https://community.oracle.com/blogs/emcmanus/2010/10/24/using-builder-pattern-subclasses

Solo estoy volviendo a copiar su solución preferida aquí. Digamos que tenemos dos clases, Shape y Rectangle. Rectangle hereda de Shape.

public class Shape 

    private final double opacity;

    public double getOpacity() 
        return opacity;
    

    protected static abstract class Init> 
        private double opacity;

        protected abstract T self();

        public T opacity(double opacity) 
            this.opacity = opacity;
            return self();
        

        public Shape build() 
            return new Shape(this);
        
    

    public static class Builder extends Init 
        @Override
        protected Builder self() 
            return this;
        
    

    protected Shape(Init init) 
        this.opacity = init.opacity;
    

Ahí está el Init clase interna, que es abstracta, y la Builder clase interna, que es una implementación real. Será útil a la hora de implementar Rectangle:

public class Rectangle extends Shape 
    private final double height;

    public double getHeight() 
        return height;
    

    protected static abstract class Init> extends Shape.Init 
        private double height;

        public T height(double height) 
            this.height = height;
            return self();
        

        public Rectangle build() 
            return new Rectangle(this);
        
    

    public static class Builder extends Init 
        @Override
        protected Builder self() 
            return this;
        
    

    protected Rectangle(Init init) 
        super(init);
        this.height = init.height;
    

Para instanciar el Rectangle:

new Rectangle.Builder().opacity(1.0D).height(1.0D).build();

De nuevo, un resumen Init clase, heredando de Shape.Inity un Build esa es la implementación real. Cada Builder clase implementar el self método, que es responsable de devolver una versión correctamente emitida de sí mismo.

Shape.Init <-- Shape.Builder
     ^
     |
     |
Rectangle.Init <-- Rectangle.Builder

Comentarios y puntuaciones del tutorial

Si te animas, tienes la opción de dejar un artículo acerca de qué le añadirías a este post.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Respuestas a preguntas comunes sobre programacion y tecnología