El siguiente patrón que vamos a ver es el patrón “Builder”. Como resumen y a modo de idea con la que nos podemos quedar en la cabeza antes de entrar en materia, este patrón consiste en hacer una separación de la construcción de objetos complejos o compuestos de su representación de modo que el mismo proceso de construcción pueda crear diferentes representaciones.
Principalmente se usa para evitar situaciones de acoplamiento. Para el que no esté familiarizado con este término, el acoplamiento define el nivel de dependencia entre clases y como buena práctica de desarrollo siempre se tiende a intentar alcanzar un nivel bajo de acoplamiento si es posible. El nivel más bajo de acoplamiento es cuando dos clases pueden funcionar una sin la otra de forma independiente. El nivel más alto, es cuando una clase no puede funcionar sin la otra, por ejemplo, cuando una clase necesita acceder a un dato contenido en la otra. Como nos estamos yendo un poquito por las ramas, si no tenéis suficiente con lo descrito aquí os recomiendo que busquéis sobre factores que permiten mejorar la programación como: acoplamiento, modularidad, cohesión…
Siguiendo con el patrón, además de la reducción del acoplamiento en nuestro sistema, persigue que tengamos diferentes representaciones de un objeto construido. Sé que todo este tema suena un poco abstracto y difícil de entender inicialmente, pero creo que más abajo con el ejemplo quedará más claro.
Este patrón de se compone de:
- Builder: Especifica la interfaz abstracta para crear las partes de un objeto Product.
- ConcreteBuilder: Construye y monta las partes de un producto a través de la implementación de la interfaz Builder, define y mantiene el registro de la representación que se crea y proporciona una interfaz para recuperar el Producto.
- Director: Construye el objeto usando la interfaz Builder
- Product: Representa al objeto complejo en construcción. El ConcreteBuilder construye la representación interna del producto y define el proceso con el que será montado. Además, incluye las clases que definen las partes que constituyen el objeto (no olvidemos que es un objeto complejo) incluyendo las interfaces para montar el resultado final.
Al final los pasos para la correcta utilización del patrón son los siguientes:
- El Cliente crea un objeto Director y lo configura con el objeto Builder.
- El Director notifica al constructor cuando una parte del Producto se debe construir.
- El Builder maneja las peticiones del Director y agrega las partes al Producto.
- El Cliente el producto del Builder.
Como consecuencias de la implementación del patrón tenemos:
- Se nos permite variar la representación interna del Producto: como el producto se construye a través de una interfaz abstracta, para cambiar la representación interna del producto basta con definir un nuevo tipo de constructor.
- Se nos permite separar el código de la construcción y la representación.
- Nos da un control muy fino sobre el proceso de construcción de un Producto.
Para el ejemplo vamos a coger una pizzería que es un ejemplo que podéis encontrar en muchos sitios, pero me ha parecido lo suficientemente descriptivo como para no tener que diseñar uno por mí mismo. En este caso vamos a tener:
- Una cocina: Director
- Las pizzas: Product
- Las diferentes variedades de pizzas: ConcreteBuilder
Ahora vamos a rellenar esto con algo de código. En primer lugar tendremos el producto:
class Pizza { private String base; private String salsa; private String ingredientes; /* Getters y Setters */ }
En segundo lugar tendremos el Builder:
abstract class PizzaBuilder { protected Pizza pizza; public Pizza getPizza() { return this.pizza; } public void createPizzaProduct() { this.pizza = new Pizza(); } public abstract void hacerBase(); public abstract void hacerSalsa(); public abstract void hacerIngredientes(); }
A partir de aquí tenemos los ConcreteBuilders que serán las pizzas específicas de cada tipo:
class Carbonara extens PizzaBuilder { public void hacerBase() { this.pizza.setBase(FINA); } public void hacerSalsa() { this.pizza.setSalsa(NATA); } public void hacerIngredientes() { this.pizza.setIngredientes(BACON + CEBOLLA); } } class Barbacoa extens PizzaBuilder { public void hacerBase() { this.pizza.setBase(GRUESA); } public void hacerSalsa() { this.pizza.setSalsa(TOMATE); } public void hacerIngredientes() { this.pizza.setIngredientes(CARNE + CARNE); } } class Mediterranea extens PizzaBuilder { public void hacerBase() { this.pizza.setBase(EXTRA_FINA); } public void hacerSalsa() { this.pizza.setSalsa(NO); } public void hacerIngredientes() { this.pizza.setIngredientes(ACEITUNA + ALCACHOFA); } }
Y por último vamos con el Director:
class Cook { private PizzaBuilder pizzaBuilder; public void setPizzaBuilder(PizzaBuilder pb) { this.pizzaBuilder = pb; } public Pizza getPizza() { return this.pizzaBuilder.getPizza(); } public void hacerPizza() { pizzaBuilder.createPizzaProduct(); pizzaBuilder.hacerBase(); pizzaBuilder.hacerSalsa(); pizzaBuilder.hacerIngredientes(); } }
Vamos a por el código de la clase Main con el que ejecutar algo y ver cómo funciona:
public class Main { public static void main(String[] args) { Cook cook = new Cook(); PizzaBuilder carbonaraB = new Carbonara(); cook.setPizzaBuilder(carbonaraB); cook.hacerPizza(); Pizza pizza = cook.getPizza(); … } }
Como nota final, hay que decir que los patrones de Abstract Factory y Builder son muy similares, ya que los dos pueden realizar la construcción de objetos complejos. La principal diferencia entre ellos es que el patrón Builder se centra en construir el objeto complejo paso a paso, mientras que el patrón Abstract Factory da más importancia a la similitud de los objetos.
Hasta aquí hemos llegado por hoy. Espero que más o menos hayamos entendido para que sirve este patrón y como implementarlo. Nos vemos.
Muy buena explicación, el ejemplo es muy claro. Ahora lo entiendo todo mucho mejor!
Gracias por tu tiempo intentando aclarar estos patrones :D, es de mucha utilidad!
LikeLike
¡Gracias! Siempre vienen bien los ánimos. Un saludo
LikeLike