Design Patterns - Abstract Factory
TLDR; El Abstract Factory es un patrón de diseño creacional que provee una interfaz para la creación de familias de objetos relacionados o dependientes sin especificar sus clases concretas.
Un Absract Factory (fabrica abstracta) es un patrón de diseño que se encarga de crear objetos, y que a diferencia del Factory Method, estos están relacionados por un tema en común sin la necesidad de especificar la clase exacta de el objeto que será creado.
¿Porque necesitamos el patrón Abstract Factory?
Como se explicaba anteriormente en el patrón Factory Method, la inicialización de un objeto se puede volver compleja de manejar. El Factory y Factory Method manejan bien casos simples, que no involucren demasiadas variaciones. Pero, que pasa cuando hay varios productos relacionadas a una familia, así como se muestra en la siguiente imagen, un ejemplo son las mascotas: Gatos, Perros, etc, y adicionalmente esa familia de productos tienen variaciones como lo pueden ser las Razas Pequeñas, Razas Grandes, en fin… Aquí se vuelve más complejo el tema.
Aquí es donde entra el patrón Abstract Factory, que nos permite manejar este tipo de relaciones con diferentes variantes, simplificando la creación de objetos sin tener que especificar clases en concreto.
En definición, ¿que es el patrón Abstract Factory?
El Abstract Factory es un patrón de diseño creacional que provee una interfaz para la creación de familias de objetos relacionados o dependientes sin especificar sus clases concretas.
El problema en un ejemplo
Supongamos debemos programar una aplicación cuya la lógica sea la de una maquina para producir bebidas. Tenemos que manejar el Café y el Té, en su proceso de creación y consumo. Aplicando el patrón Abstract Factory iniciaremos creando una clase base llamada HotDrink
, pero como en Javascript no existen cosas como clases abstractas, la forma de implementarlo aquí es que se creará una clase y se dejan las funciones en blanco. Después se crearán las clases Tea
y Coffee
que extiendan de HotDrink
por medio de la palabra reservada extends.
1 | {% raw %}class HotDrink { |
En Javascript, las clases abstractas no se comportan como una miembro abstracto como lo hace en C# o Java, pero realizar una Jerarquía de la clase HotDrink
ayuda a entender que estas clases están relacionadas.
Hasta este punto tenemos una jerarquía de HotDrinks
, lo que haremos a continuación será tener una jerarquía de factories que se encargará de construir estas bebidas. Si pensamos en el café o en el té, un CoffeeFactory o un TeaFactory pueden tener la misma jerarquía, para eso crearemos las 2 factories encargadas de crear el té y el café, y su clase base llamada HotDrinkFactory
.
1 | {% raw %}class HotDrinkFactory { |
Ahora que tenemos construidos las factories, queremos poder consumirlo; para eso construiremos la maquina de hacer bebidas calientes HotDrinkMachine
y poder interactuar con las clases creadas.
1 | {% raw %}class HotDrinkMachine { |
Con esta implementación, no estamos en realidad haciendo uso del patrón Abstract Factory, solamente estamos explícitamente usando esas factories a mano, el cual es un acercamiento bastante mal porque estaríamos violando el principio Open–closed (Caso similarmente visto en el patrón Factory Method). Esto está mal porque siempre que tengamos un nuevo factor tendríamos que modificar el método makeDrink
para soportarlo. Aunque esto es un punto de partida.
Lo que queremos lograr, es tener un tipo de asociación entre la factoría y el objeto que actualmente hace HotDrinkMachine
, que por el momento es realizado manualmente. Para realizar esto, crearemos un enumerador llamado AvailableDrink
para tener una especie de lista con todos los diferentes tipos que vamos a almacenar; después instanciamos cada factory en el constructor()
y hacemos una modificación en el método makeDrink()
.
1 | {% raw %}let AvailableDrink = Object.freeze({ |
Listo, ahora que hemos creado las clases necesarias, usaremos la implementación.
1 | {% raw %}// Ahora para poder usar la maquina correctamente. |
Básicamente algunas veces terminamos con situaciones que presentan una jerarquía de tipos, en este caso tenemos la jerarquía de HotDrink
que está hecha por té y café. Y se tiene la correspondiente jerarquía de factories, HotDrinkFactory
y se tiene diferentes formas de construir un té y un café.
Código completo del ejemplo: https://bit.ly/3cDsmKL
Estructura del patrón Abstract Factory
En este patrón encontramos varios componentes que interactuaran entre si.
- Los Abstract Products, que en el ejemplo es la interfaz
HotDrink
es el lugar donde se declaran los productos relacionados que se van a utilizar. En nuestro caso sabemos que todos los productos que utilizaremos implementarán la claseconsume()
. - Los Concrete Products, que son las clases
Coffee
yTea
. Básicamente son las implementaciones de la clase abstractaHotDrink
, en donde se tendrán todas las variantes. - El Abstract Factory, que en nuestro ejemplo es la interfaz
HotDrinkFactory
, es el lugar que declara un conjunto de métodos para crear cada uno de los productos abstractos. - Los Concrete Factories, que en nuestro ejemplo son las implementaciones
TeaFactory
yCoffeeFactory
. Básicamente son las clases que implementan los métodos de creación del Abstract FactoryHotDrinkFactory
. Cada factory en concreto corresponde a una variación especifica del producto y solamente crea esa variante del producto. - El cliente, en este caso, sería mucho más sencillo de implementar debido a que no es necesario conocer toda la especificación interna. De esta forma, el código del cliente que usa una fabrica no se va a acoplar a ninguna variante en especifico.
Conclusiones
- La jerarquía de factories puede ser usada para crear objetos relacionados. Ya que se pueden encapsular un grupo de factories individuales con una meta en común.
- Abstract Factory logra generar una separación de los detalles de la implementación de un grupo de objetos para un uso en general.
- Los Abstract Factory son muy útiles cuando tu código tiene varias familias de productos relacionados, pero quieres que la dependencia entre ellos no sea a una clase en concreto. Siendo muy oportuno cuando se quiere extender el software sin modificarlo (Open/Closed Principle) y extraer los productos en un solo lugar (Single Responsibility Principle).
- Usar cuando realmente sea necesario y tenga una jerarquía de factories, porque de lo contrario causarás que tu código sea más complicado de lo que ya es.
Ejercicio
Se requiere que se construya una solución para poder servir alimentos a perros y gatos, así como se muestra en el siguiente imagen:
Para poder integrar los productos relacionados y las familias, se requiere crear una solución aplicando el patrón Abstract Factory, para que se pueda fácilmente brindar alimentos dependiendo en la familia.
Solucion Esperada.
Recursos adicionales y bibliografía
https://www.dofactory.com/javascript/factory-method-design-pattern
https://addyosmani.com/resources/essentialjsdesignpatterns/book/
https://loredanacirstea.github.io/es6-design-patterns/#abstract-factory