En el post anterior vimos cómo usar Factorias y las ventajas que estas pueden aportar sobre los constructores
En este post vamos a ver otra forma de construir objetos, los Builders.
"Consider a builder when faced with many constructor parameters"
Tanto el uso de factorías como el de constructores tienen un problema cuando el número de parámetros para construir una clase empieza a ser elevado
- NOTE
-
Y cúando es elevado? pues cada cual tendrá su opinión pero en mi opinión más de tres parámetros ya es una señal de que esa clase va a necesitar más bien pronto que tarde otro parámetro más y sería buena idea aplicar un Builder
- INFO
-
Cuando, tanto con factorías como constructores, empiezas a crear métodos con parámetros necesarios y luego otros métodos en los que vas añadiendo cada vez un parámetro opcional se llama "telescoping constructor"
class A{
public static A newInstance(){
return new A();
}
public static A newInstance(String nombre){
return new A(nombre);
}
public static A newInstance(String nombre, int edad){
return new A(nombre, edad...);
}
public static A newInstance(String nombre, int edad, Date nacimiento){
return new A(nombre, edad...);
}
public static A newInstance(String nombre, int edad, Date nacimiento, Ciudad ciudad){
return new A(nombre, edad...);
}
...
}
Otra forma de inicializar objetos cuando el número de parámetros es numeroso es aplicando la aproximación de JavaBeans
creando métodos get/set para cada propiedad de la instancia. Sin embargo esta forma tiene muchos problemas como por ejemplo la validación de que todos los parámetros han sido proporcionados, es muy "verbose", dificil de seguir y no puede crear objetos inmutables.
Builder
Por todo ello el otro patrón que se aplica es creando un Builder
class A{
private final int prp1;
private final int prp2;
...
private final int prpn;
public int getPrp1() { return prp1;}
// NO tenemos metodo setPrp1
public static class Builder{
private final int prp1;
private final int prp2;
...
private final int prpn;
public Builder(){}
public Builder prp1(int p){ prp1 = p; return this;}
...
public Builder prpn(int p){ prpn = p; return this;}
public A build(){
// validar que tenemos todos los parametros necesarios
return new A(this);
}
}
private A(Builder builder){
prp1 = builder.prp1;
...
prpn = builder.prpn;
}
}
y el código para crear una instancia A sería:
A a = new A.Builder().prpn(n).prp2(2).prp1(1).build();
- TIP
-
Si por ejemplo la clase requiere de unos parámetros requeridos es fácil pedirlos en el constructor del Builder y declarar así explicitamente que son necesarios.
"The Builder pattern is well suited to class hierarchies"
Esta capacidad del patrón Builder no es fácil de ver de primeras (al menos a mí) pero es muy potente
Cuando tenemos una jerarquía de objetos tipo clase base "A" y otras clases que extienden de ella, "B" y "C", podemos aplicar el patrón Builder usando genéricos de tal forma que reutilizemos al máximo los builders
public abstract class A{
abstract static class Builder<T extends Builder<T>>{
public Builder(){
}
public T prp1(int prp1){
this.prp1 = prp1;
return self();
}
abstract A build();
protected abstract T self();
}
private A(Builder<?> builder){
// copio los atributos de A
}
}
public class B extends A{
public static class Builder extends A.Builder<Builder>{
public Builder(){
}
@Override public B build(){
return new B(this)
}
public T prp2(int prp2){
this.prp2 = prp2;
return self();
}
@Override protected Builder self(){ return this;}
}
private B(Builder builder){
// copio los atributos de B
}
}
El "truco" se encuentra en el Generic de la clase A: Builder<T extends Builder<T> >
así como en el métodoself
. Este es usado internamente por el builder para permitir al compilador poder concatenar las llamadas
A a = new B.Builder().prp1(1).prp2(2).build
Top comments (0)