Builder (patrón de diseño)
Como patrón de diseño, el patrón builder (Constructor) se usa para permitir la creación de una variedad de objetos complejos desde un objeto fuente (Producto), el objeto fuente se compone de una variedad de partes que contribuyen individualmente a la creación de cada objeto complejo a través de un conjunto de llamadas secuenciales a una implementación específica que extienda la clase Abstract Builder. Así, cada implementación existente de Abstract Builder construirá un objeto complejo Producto de una forma diferente deseada.
El patrón builder es creacional.
A menudo, el patrón builder construye el patrón Composite, un patrón estructural.
Intención: Abstrae el proceso de creación de un objeto complejo, centralizando dicho proceso en un único punto, de tal forma que el mismo proceso de construcción pueda crear representaciones diferentes.
Diagrama de clases
editar- Builder
- clase abstracta para crear productos.
- Concrete Builder
- implementación del Builder
- construye y reúne las partes necesarias para construir los productos
- Director
- construye un objeto, un producto concreto, usando el patrón Builder configurando el Concrete Builder a usar.
- Producto
- El objeto complejo bajo construcción
Ventajas
editar• Reduce el acoplamiento.
• Permite variar la representación interna de estructuras complejas, respetando la interfaz común de la clase Builder.
• Se independiza el código de construcción de la representación. Las clases concretas que tratan las representaciones internas no forman parte de la interfaz del Builder.
• Cada ConcreteBuilder tiene el código específico para crear y modificar una estructura interna concreta.
• Distintos Director con distintas utilidades (visores, parsers, etc) pueden utilizar el mismo ConcreteBuilder.
• Permite un mayor control en el proceso de creación del objeto. El Director controla la creación paso a paso, solo cuando el Builder ha terminado de construir el objeto lo recupera el Director.
Ejemplos
editarJava
editar/** "Producto" */
class Pizza {
private String masa;
private String salsa;
private String relleno;
public void setMasa(String masa) { this.masa = masa; }
public void setSalsa(String salsa) { this.salsa = salsa; }
public void setRelleno(String relleno) { this.relleno = relleno; }
}
/** "Abstract Builder" */
abstract class PizzaBuilder {
protected Pizza pizza;
public Pizza getPizza() { return pizza; }
public abstract void buildMasa();
public abstract void buildSalsa();
public abstract void buildRelleno();
}
/** "ConcreteBuilder" */
class HawaiPizzaBuilder extends PizzaBuilder {
public HawaiPizzaBuilder(){super.pizza = new Pizza();}
public void buildMasa() { pizza.setMasa("suave"); }
public void buildSalsa() { pizza.setSalsa("dulce"); }
public void buildRelleno() { pizza.setRelleno("chorizo+alcachofas"); }
}
/** "ConcreteBuilder" */
class PicantePizzaBuilder extends PizzaBuilder {
public PicantePizzaBuilder(){super.pizza = new Pizza();}
public void buildMasa() { pizza.setMasa("cocido"); }
public void buildSalsa() { pizza.setSalsa("picante"); }
public void buildRelleno() { pizza.setRelleno("pimienta+salchichón"); }
}
/** "Director" */
class Cocina {
private PizzaBuilder pizzaBuilder;
public void setPizzaBuilder(PizzaBuilder pb) { pizzaBuilder = pb; }
public Pizza getPizza() { return pizzaBuilder.getPizza(); }
public void construirPizza() {
pizzaBuilder.buildMasa();
pizzaBuilder.buildSalsa();
pizzaBuilder.buildRelleno();
}
}
/** Un cliente pidiendo una pizza. */
class BuilderExample {
public static void main(String[] args) {
Cocina cocina = new Cocina();
PizzaBuilder hawai_pizzabuilder = new HawaiPizzaBuilder();
PizzaBuilder picante_pizzabuilder = new PicantePizzaBuilder();
cocina.setPizzaBuilder( hawai_pizzabuilder );
cocina.construirPizza();
Pizza pizza = cocina.getPizza();
}
}
/**
* 2da opción para el abstract builder quizá más transparente para su uso.
* Dentro del crear se llaman los métodos build.
* Es válido siempre y cuando no se necesite alterar
* el orden del llamado a los "build's".
*/
abstract class OtroPizzaBuilder {
protected Pizza pizza;
public Pizza getPizza() { return pizza; }
public void crearNuevaPizza() {
pizza = new Pizza();
buildMasa();
buildSalsa();
buildRelleno();
}
public abstract void buildMasa();
public abstract void buildSalsa();
public abstract void buildRelleno();
}
/** "Director" */
class OtraCocina {
private OtroPizzaBuilder pizzaBuilder;
public void construirPizza() {
pizzaBuilder.crearNuevaPizza();
//notar que no se necesita llamar a cada build.
}
}
Otro ejemplo sobre este patrón
public class BuildPattern {
public void main(String args[]){
MakeAMovie makeAMovie = new MakeAMovie(new Rambo());
makeAMovie.filmMovie();
Movie movie = makeAMovie.seeMovie();
System.out.println(movie.genere);
System.out.println(movie.name);
System.out.println(movie.duration);
}
/**
* First step we need the movie
*/
public class Movie{
public String name;
public String genere;
public int duration;
public void setName(String name){
this.name=name;
}
public void setGenere(String genere){
this.genere=genere;
}
public void setDuration(int duration){
this.duration=duration;
}
}
/**
* Second step: we need a studio to make the movie
*/
abstract class BuilderMovieStudioAbstract{
Movie movie = new Movie();
abstract void buildName();
abstract void buildGenere();
abstract void buildDuration();
Movie getMovie(){
return movie;
}
}
/**
* Third step: we need an idea to film that movie
*/
public class Rambo extends BuilderMovieStudioAbstract{
void buildDuration() {
movie.setDuration(120);
}
void buildGenere() {
movie.setGenere("Action");
}
void buildName() {
movie.setName("Rambo");
}
}
/**
* Last Step: we make the movie
*/
public class MakeAMovie{
private final BuilderMovieStudioAbstract abstractMovie;
public MakeAMovie(BuilderMovieStudioAbstract abstractMovie) {
this.abstractMovie=abstractMovie;
}
public void filmMovie() {
abstractMovie.buildName();
abstractMovie.buildGenere();
abstractMovie.buildDuration();
}
public void seeMovie() {
abstractMovie.getMovie();
}
}
}
Clarificaciones
editarReutilización de instancias
editarCuando uno ve la estructura del patrón, incluido el diagrama UML, y sobre todo tras observar la clase Cocina del primer ejemplo Java se podría pensar que el patrón Builder a su vez usa del patrón State pero orientado a la creación de objetos únicamente pero no esto no es, para nada, indispensable.
Hay que advertir siguiendo el nombrado de clases de los ejemplos, que los elementos Concrete Builder de la primera opción del primer ejemplo (HawaiPizzaBuilder y PicantePizzaBuilder) y el elemento Director (MakeAMovie) del segundo ejemplo solo deben usarse una vez y ser desechados ya que cualquier otro intento posterior de volver a utilizarlos llamando al método de construcción del Director estaría obteniéndose de nuevo el Producto anterior y no una nueva instancia. Así, se debe instanciar cada vez que se quiera construir un nuevo Producto, bien el Concrete Builder o bien el Director respectivamente.
Esto no pasa con la segunda opción del primer ejemplo ya que el elemento Builder devuelve una nueva instancia cada vez que es llamado por el método de construcción del Director y, así, se puede decir que cumple el patrón State y que su uso puede generar menos errores aunque las otras opciones siguen siendo perfectamente válidas. Lo único que objetar es que esta segunda opción sigue sin ser thread-safe, ninguna de ellas lo es; si quisiéramos aportarle este concepto de una forma fácil se puede usar Almacenamiento thread-local o hacer que los métodos de cada Concrete Builder reciban como argumento el producto concreto a modificar.
Terminología Builder
editarEn los últimos años, se ha extendido la terminología Builder al concepto de autogeneración, o de forma manual, de código en clases,[1][2] donde se añaden métodos Setters, uno por cada atributo de la clase y de nombre igual a la variable, donde además de configurar el valor devuelven la propia instancia del objeto permitiendo el encadenamiento de llamadas sucesivas para que de una forma compacta, declarativa y legible se pueda aportar los valores requeridos en la instancia. Además, incluso se puede proveer un método Builder en la clase que construya toda la clase. Si bien esto es una buena metodología para generar código limpio no debe confundirse con el patrón definido ya que fácilmente se puede chequear que no aporta los elementos necesarios del diagrama de clases ni, por ende, con las colaboraciones necesarias entre estos.
Si bien es cierto que una implementación no demasiado estricta del patrón de diseño Builder podría hacer uso internamente de esta metodología.[3]