En esta ocasión vamos a analizar el Patrón Fábrica Abstracta. En el post anterior, logramos definir el estándar para crear instancias de Laptop en cada sucursal. Ese diseño nos garantiza flexibilidad y la posibilidad de agregar nuevas sucursales para crear las diferentes marcas (implementaciones) de Laptop.
Sin embargo, el modelo anterior también nos impide controlar cómo se ensamblan las diferentes marcas de Laptop. Por ejemplo, el tipo de memoria RAM para una DELL talvéz no sea el mismo para una ASUS. O el tipo de disco duro o tarjeta de video.
Además, ¿cuál es la diferencia entre una Laptop DELL con una ASUS, ACER o cualquier otra marca? Al final todas son Laptops, ¿cierto?
Lo que las hace diferentes en realidad, son sus componentes internos (RAM, Disco Duro, Tarjeta de Video, etc).
Entonces, podríamos crear fábricas de componentes para cada marca, en lugar de crear subclases de Laptop. Con esto nos garantizamos que TODAS las sucursales usan los mejores componentes del mercado para ensamblar las computadoras y así evitar el uso de versiones genéricas o de mala calidad.
Veamos el diseño del diagrama de clases actualizado.
![Diagrama de clases actualizado para patrón fábrica abstracta](https://i0.wp.com/runnablepatterns.com/wp-content/uploads/2019/07/01_AbstractFactory_Class.png?resize=780%2C607&ssl=1)
Veamos los cambios que hicimos:
Para empezar, ya no utilizamos diferentes instancias de Laptop for cada tienda. Ya que, lo que cambia en cada una, son sus componentes internos.
Agregamos un método abstracto a la clase Laptop llamado assemblyParts(), el cual, se encargará de realizar el ensamblado de las partes para cada Laptop. Este método no le interesa el tipo de parte que se esté agregando, solamente conoce que existen 3 partes: Disco Duro, RAM y Video.
/**
* Assembly all Laptop parts
*/
public abstract void assemblyParts();
Creamos una fábrica nueva llamada LaptopPartFactory, la cual es una interfaz. Es aquí donde ponemos en práctica el patrón como tal. Las implementaciones de cada tienda (LaptopStoreCRC y LaptopStoreUSA) tienen una referencia solamente a la interfaz no a una implementación concreta.
public interface LaptopPartFactory {
public HardDrive assemblyHardDrive();
public RAM assemblyRAM();
public VideoCard assemblyVideoCard();
}
public class LaptopPartFactoryCRC implements LaptopPartFactory {
public HardDrive assemblyHardDrive() {
return new HardDriveCRC();
}
public RAM assemblyRAM() {
return new RAMCRC();
}
public VideoCard assemblyVideoCard() {
return new VideoCardCRC();
}
}
public class LaptopPartFactoryUSA implements LaptopPartFactory {
public HardDrive assemblyHardDrive() {
return new HardDriveUSA();
}
public RAM assemblyRAM() {
return new RAMUSA();
}
public VideoCard assemblyVideoCard() {
return new VideoCardUSA();
}
}
Al momento de crear la Laptop, por medio del método createLaptop, definimos el tipo de fábrica a utilizar para las partes. Ese tipo lo enviamos al constructor de cada Laptop específica que vamos a crear.
public class BigLaptop extends Laptop {
/**
* Variable to store the Part Factory implementation
*/
private LaptopPartFactory partFactory;
/**
* Overloaded constructor
* @param _name Laptop name value
* @param _partFactory Part Factory implementation
*/
public BigLaptop(String _name, LaptopPartFactory _partFactory) {
super(_name);
this.partFactory = _partFactory;
}
@Override
public void assemblyParts() {
System.out.println(String.format("[Log] starting part assembly for Big Laptop: %s", this.getName()));
System.out.println(this.partFactory.assemblyHardDrive().getDescription());
System.out.println(this.partFactory.assemblyRAM().getDescription());
System.out.println(this.partFactory.assemblyVideoCard().getDescription());
}
}
En cada implementación de LaptopStore enviamos la fábrica de partes.
@Override
public Laptop createLaptop(LaptopTypeEnum laptopType) {
Laptop laptop = null;
LaptopPartFactory partFactory = new LaptopPartFactoryCRC();
switch(laptopType) {
case SMALL:
laptop = new SmallLaptop(this.getLaptopName(), partFactory);
break;
// other laptops creation goes here
}
return laptop;
}
De esta forma, logramos delegar, a las implementaciones de la fábrica, la creación de partes de manera dinámica por tienda.
En el libro Head First Design Patterns, nos dan una definición para el patrón Fábrica Abstracta:
Fábrica Abstracta brinda una interfaz, para la creación de familias de objetos relacionados u objetos dependientes sin necesidad de definir su clase concreta.
Este sería el diagrama sugerido según Head First Design Patterns.
![Diagrama sugerido para el patrón fábrica abstracta](https://i0.wp.com/runnablepatterns.com/wp-content/uploads/2019/07/02_AbstractFactory_Diagram.png?resize=780%2C352&ssl=1)
Al ejecutar la aplicación de este patrón, obtenemos el siguiente resultado.
![Resultado de ejecutar el código de ejemplo](https://i0.wp.com/runnablepatterns.com/wp-content/uploads/2019/07/03_AbstractFactory_output.png?resize=554%2C505&ssl=1)
Como podemos observar, cada tienda envía la fábrica de partes específica para agregarlas de manera dinámica.
Si en un futuro necesitamos agregar una tienda adicional, ya tendríamos un framework para hacerlo. Los métodos que se encargan de la creación de instancias de Laptop y de las partes ya estarían listos, solamente tendríamos que implementar las características específicas de la nueva tienda.
El código de ejemplo del patrón fábrica abstracta lo pueden encontrar en este link.
En el próximo post vamos a analizar el patrón Comando.
Recordá suscribirte aquí para recibir las últimas actualizaciones todas las semanas.
Referencias:
Libro Head First Design Patterns. Versión digital.