El patrón fábrica simple es muy usado en la mayoría de frameworks de hoy en día, bueno, en realidad es Fábrica Abstracta, pero Fábrica Simple es el punto de partida.
En el post anterior, estuvimos hablando de un principio de diseño al que llamamos “Abierto – Cerrado”, el cual, en resumen, nos dice que debemos pensar en cerrar nuestras clases a modificaciones, pero mantenerlas abiertas para extensiones.
Esto suena extraño, ¿no? ¿Cómo podemos cumplir este principio si lo único que es igual en cualquier aplicación son las modificaciones o cambios?
La respuesta en realidad es sencilla, no es posible cumplir con ese principio al 100%, pero lo que sí podemos hacer es, reducir o centralizar las modificaciones en un único punto. Es aquí donde entra Fábrica Simple.
Imaginen, ¿qué pasaría si para comprar un vehículo, existieran diferentes lugares que se encarguen de fabricarlos? Por ejemplo, que cada dealer se encargue de la fabricación de los autos y tengan su propia versión. ¿Cómo sería si necesito un repuesto o cambiar alguna parte del vehículo? Tendría que ir siempre al mismo lugar a hacerlo. ¿Por qué, se preguntarán? Pues la respuesta es: no habría un estándar para la fabricación.
Cada sucursal tendría su propia forma de ensamblaje, sus propias partes, diferentes procesos, diferentes tamaños de partes, etc. En otras palabras, sería un caos tratar de obtener una parte de cualquier otro proveedor, que no sea el que fabricó el vehículo.
En nuestras aplicaciones a veces pasa lo mismo. ¿Cuántas veces utilizamos new para crear la clase que se encarga de la lógica del negocio? Esa clase que creamos infinitas veces en cada llamada al servicio web, en cada controlador, en cada prueba, etc.
¿Cómo sabemos que estamos utilizando el constructor correcto, o que esa es la forma correcta de crear la clase que necesitamos?
Para evitar estos problemas se utiliza una fábrica. Así como en el ejemplo de los vehículos, los dealers no se encargan de fabricar el vehículo, para eso está la fábrica de cada marca.
En nuestras aplicaciones, podemos crear una fábrica que se encargue de crear el objeto que necesitamos, de manera correcta, y además, de la forma correcta, en un único punto. Con esto nos quitamos de encima problemas de creación de clases y además podemos agregar lógica para determinar qué clase crear.
Vamos a usar como ejemplo el diagrama del post anterior.
![Diagrama utilizado en el post del patrón decorador](https://i0.wp.com/runnablepatterns.com/wp-content/uploads/2019/06/06_DecoratorPattern_example.png?resize=600%2C379&ssl=1)
Si queremos inicializar un objeto del tipo SmallLaptop, el código sería algo parecido a esto
// creates a new SmallLaptop object
Laptop laptop = new SmallLaptop();
Ahora bien, en nuestra aplicación tenemos una clase que se encarga de procesar la Laptop antes de ser enviada al comprador, por ejemplo, hacer una revisión general, instalar el sistema operativo, empacarla y finalmente agregar el destino.
/**
* Method that creates the specific laptop type and then process it before shipping it to client
* @param laptopType The specific laptop type to create
* @return The specific instance of Laptop
*/
public Laptop processLaptop(String laptopType) {
// variable to store the laptop type
Laptop laptop = null;
// logic to create specific laptop type
if(laptopType = "Small") {
laptop = new SmallLaptop();
} else if(laptopType = "Medium") {
laptop = new MediumLaptop();
} else {
laptop = new BigLaptop();
}
// process the laptop
review(laptop);
installOS(laptop);
pack(laptop);
destination(laptop);
// return processed laptop
return laptop;
}
Esta sección del código tiene dos funcionalidades, la primera, crear el tipo de Laptop requerido según se necesita, y segundo, realizar el proceso necesario para la laptop. ¿No les parece que estamos haciendo algo mal? ¿Dónde quedó el principio Abierto – Cerrado? ¿Qué debemos cambiar del código anterior?
Primero, necesitamos identificar qué partes son las que siempre pueden cambiar. ¿Qúe pasa si creamos otro tipo de Laptop, por ejemplo SpecialLaptop?
Si mantenemos el código anterior, tendríamos que agregar otro if para verificar el tipo y crear la instancia necesaria para SpecialLaptop. En este ejemplo solo tenemos el método processLaptop(), pero ¿qué pasaría si tenemos más métodos que, de igual forma, necesitan crear instancias de Laptop según el tipo?
Correcto, tendríamos código duplicado por toda la aplicación, que será difícil de mantener y difícil de realizar cambios y más propenso a errores, ya que, se deben replicar los cambios en todos los puntos.
En este caso, debemos crear una nueva clase llamada LaptopSimpleFactory, que es la que se va a encargar de crear el tipo específico de Laptop según sea necesario.
/**
* Simple Factory class to create Laptop instances
*/
public class LaptopSimpleFactory {
/**
* Creates a new Laptop according to the specific type
* @param laptopType The specific laptop to create
* @return The specific Laptop instance
*/
public Laptop createLaptop(String laptopType) {
// variable to store laptop instance
Laptop laptop = null;
// logic to create specific laptop type
if(laptopType = "Small") {
laptop = new SmallLaptop();
} else if(laptopType = "Medium") {
laptop = new MediumLaptop();
} else {
laptop = new BigLaptop();
}
// return specific laptop
return laptop;
}
}
De esta forma es como logramos consolidar la lógica de creación de objetos en un único punto. Si en un futuro nos piden agregar más tipos de laptops, de igual forma habría que modificar el código anterior, pero la ventaja es que sólo sería en esta clase.
/**
* Class that handles operations over Laptop class
*/
public class LaptopCenter {
/**
* Store the factory instance
*/
private LaptopSimpleFactory laptopFactory;
/**
* Overloaded constructor that receives the specific factory to use
* @param _laptopFactory The factory to use to create objects
*/
public LaptopCenter (LaptopSimpleFactory _laptopFactory) {
// set the factory type
laptopFactory = _laptopFactory;
}
/**
* Method that creates the specific laptop type and then process it before shipping it to client
* @laptopType Specific laptop to create
*/
public Laptop processLaptop (String laptopType) {
// store the laptop type
Laptop laptop = null;
// create the laptop using the factory
laptop = laptopFactory.createLaptop(laptopType);
// process the rest of laptop
review(laptop);
installOS(laptop);
pack(laptop);
destination(laptop);
// return the created laptop
return laptop;
}
}
El diagrama de clases sería de la siguiente manera.
![Diagrama de clases implementando el patrón fábrica simple](https://i0.wp.com/runnablepatterns.com/wp-content/uploads/2019/06/02_SimpleFactoryDiagram.png?resize=780%2C335&ssl=1)
En la clase demo de este patrón, simplemente creamos una instancia de LaptopCenter, y enviamos al constructor la fábrica que vamos a utilizar, que sería un objeto de tipo LaptopSimpleFactory.
// send a new instance of the required factory to LaptopCenter class
LaptopCenter laptopCenter = new LaptopCenter(new LaptopSimpleFactory());
// process a medium laptop
laptopCenter.processLaptop("Medium");
¿Qué pasa si enviamos un tipo que no existe?
Si mantenemos el código como esta, es posible que se introduzca un error al momento de estar ejecutándose la aplicación. Esto debido a que el método processLaptop(), recibe como parámetro un String.
Podemos mejorar un poco la aplicación si utilizamos una Enumeración para enviar como parámetro el tipo de Laptop que necesitamos crear.
/**
* Class to handle different Laptop types for instantiation
*/
public enum LaptopTypeEnum {
SMALL,
MEDIUM,
BIG
}
Al utilizar enumeraciones, garantizamos que sólo procesamos los valores esperados y limitamos las opciones. De esta forma nos evitamos errores al momento de ejecución. Ahora solo llamamos al método enviando el valor de la enumeración que necesitamos.
// process a medium laptop
laptopCenter.processLaptop(LaptopTypeEnum.MEDIUM);
El resultado de ejecutar el código anterior es el siguiente (nótese que estamos re utilizando las clases que creamos en el patrón anterior).
![Resultado de ejecutar el patrón fábrica simple.](https://i0.wp.com/runnablepatterns.com/wp-content/uploads/2019/06/01_SimpleFactory_output.png?resize=499%2C182&ssl=1)
El código de ejemplo del patrón fábrica simple lo pueden encontrar en este link.
En el próximo post vamos a analizar el patrón Fábrica o Método Fábrica.
Recordá suscribirte aquí para recibir las últimas actualizaciones todas las semanas.
Referencias:
Libro Head First Design Patterns. Versión digital.