es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

Java: Clase abstracta con patrón builder

Espero que esto no sea una duplicación; sé que hay algunas preguntas con nombres similares, pero ninguna de ellas parecía resolver mi problema.

Tengo múltiples clases que son exactamente iguales, excepto por un método específico. Por lo tanto, decidí crear una clase padre abstracta, llamémosla “A”. “A” ejecuta todo el código y tiene un método “calculate()”, que todas las subclases implementan.

Hasta aquí todo bien. El problema es que todas las subclases tienen muchos parámetros opcionales, así que en lugar de crear muchos constructores diferentes, decidí utilizar el siguiente patrón de construcción:

public abstract class A {
   private int foo;
   private int bar;

   public static class Builder {
      private int foo;
      private int bar;

      public Builder(int foo) {
         this.foo = foo;
      }

      public Builder bar(int bar) {
         this.bar = bar;
         return this;
      }

      public A build() {
         return new A(this);
      }
   }

   private A(Builder builder) {
      this.foo = builder.foo;
      this.bar = builder.bar;
   }
}

Utilicé este patrón específico en todo mi proyecto y funcionó bien, siempre y cuando la clase no sea abstracta. El problema es que el método “build()” devuelve un objeto “A”, pero “A”, por supuesto, no se puede instanciar.

Entonces, me parece que hay dos soluciones, pero ninguna de ellas parece óptima:

  1. Mover la clase “Builder” hacia las subclases de “A”, para que puedan ser instanciadas. Esto resultaría en mucho código redundante, ya que todos los atributos que establece el constructor son idénticos.

  2. Hacer que “A” no sea abstracta. Pero eso permitiría que otro usuario la instancie, a pesar de que ese no es su propósito previsto.

¿Me estoy perdiendo algo? Siento que debería haber una solución más elegante, pero no puedo pensar en una en este momento… Gracias.

Tags:  , ,

Answer

  1. Avatar for davy.ai

    Respuesta

    Puedes usar los genéricos autorreferenciales en Java para resolver este problema. Necesitas modificar tu clase A y su constructor de la siguiente manera:

    public abstract class A<t extends=""></t><t>> {
       private int foo;
       private int bar;
    
       public static class Builder<t extends=""></t><t>> {
          private int foo;
          private int bar;
    
          public Builder(int foo) {
             this.foo = foo;
          }
    
          public Builder<t> bar(int bar) {
             this.bar = bar;
             return this;
          }
    
          public abstract T build();
    
          protected void build(T object) {
              object.foo = this.foo;
              object.bar = this.bar;
          }
       }
    
       protected A() {}
    
       protected A(Builder<t> builder) {
          builder.build((T)this);
       }
    
       public abstract void calculate();
    }
    

    Aquí, T extends A<t> especifica que una subclase de A debe ser pasada como parámetro de tipo T. Esto asegura que cualquier constructor construido para una subclase de A solo puede construir objetos de esa subclase. El constructor ahora tiene un método abstracto build() que devuelve el tipo T, y un método protegido build(T object) que establece los campos del objeto. Los constructores de la clase A se han modificado para adaptarse a este cambio, y el método calculate() se deja abstracto para ser definido en las subclases.

    Ahora, puedes crear el constructor para una subclase de A de la siguiente manera:

    public class B extends A<b> {
        private int baz;
    
        public static class Builder extends A.Builder<b, builder=""> {
            private int baz;
    
            public Builder(int foo) {
                super(foo);
            }
    
            public Builder baz(int baz) {
                this.baz = baz;
                return this;
            }
    
            @Override
            public B build() {
                B b = new B();
                super.build(b);
                b.baz = this.baz;
                return b;
            }
        }
    
        protected B() {}
    
        protected B(Builder builder) {
            super(builder);
            this.baz = builder.baz;
        }
    
        @Override
        public void calculate() {
            // implementación
        }
    }
    

    Aquí, la clase B extiende A<b> para especificar el parámetro de tipo correcto. La clase Builder extiende A.Builder<b, builder=""> para especificar que construye objetos de B. El método build() se sobrescribe para construir un nuevo objeto B y establecer sus campos utilizando el método build() del constructor de la superclase y los propios campos del constructor. Los constructores de la clase B se modifican para adaptarse a este cambio, y se define el método calculate() según sea necesario.

    Esta solución elimina la necesidad de código de constructor redundante y asegura que solo se construyan las subclases previstas de A.</b,></b,>

Comments are closed.