El patrón Factory
Puede que no te diga mucho la frase anterior, pero eso es porque no has visto todavía las ventajas de una constante en todos los patrones de diseño: el desacoplamamiento entre componentes. Por ejemplo, imaginemos que una clase llamada Empresa necesita devolver un objeto en función de un input del usuario por teclado.
La clase Main y la clase Electrodoméstico están altamente acopladas. ¿Por qué? Porque un cambio en los electrodomésticos (la desaparición de un producto, la inclusión de otro) obliga a modificar el código fuente de la clase Main. Por eso, el primer paso para hacer un buen código es separar lo que varía de lo que permanece constante, y de esta forma generar espacios de código donde el mantenimiento del programa esté más controlado.
Veamos la representación del anterior snippet de código en un diagramas UML donde los componentes están altamente acoplados.
Vemos que la clase Main depende de tres clases (y estas clases pueden variar con el tiempo) por lo que tenemos el problema de que nuestro núcleo de la aplicación, el componente Main, va a ser cada vez más complicado de escalar a medida que avanza la vida de la aplicación. Es urgente separar el componente principal de esta funcionalidad ¿pero como lo hacemos? Creando otro componente.
Volviendo al código anterior: ¿que funcionalidades vemos en el programa? Principalmente dos: la entrada y salida al usuario de los datos y la creación de los objetos.
Por supuesto, podemos separar casi tanto como queramos las funciones en funciones más concretas, pero esta entrada no pretende tratar sobre arquitectura de software sino sobre el patrón factory, que consiste ni más ni menos que en crear un componente cuya función se la de la instanciar objetos.
Ahora tenemos una clase llamada ElectroCreator que se encarga de instanciar las clases concretas en función de un parámetro que le pasa la clase Main. Main ya no tiene que saber cómo crear el objeto, no es su función, sólo sabe que puede llamar al método de un componente que sí le devuelve un objeto. Y cómo lo haga le da igual mientras funcione.
Crear un componente por cada funcionalidad de la aplicación es en programación orientada objetos el abc de cualquier proyecto. Nos da muchísima más flexibilidad a la hora de mantener y testear, dado que está altamente aislado de otros componentes con los que interactúa. En desarrollo esto se traduce en una mayor independencia entre equipos y por tanto una mayor productividad. ¡Pero de alguna forma tienen que comunicarse los componentes entre sí! Si estuvieran completamente aislados no podrían hacer nada en conjunto. Efectivamente, y esto se hace gracias a elementos que abstraen el componente de su funcionalidad concreta: las interfaces.
Las interfaces nos permiten decirle a un objeto: «puedes ejecutar los siguientes métodos sobre esta otra serie de objetos, y no sabrás cómo lo hacen, pero funcionarán como esperas que funcionen y/o te retornarán el valor que esperas que retornen». ¡No más código funcionalmente distinto en la misma clase! ¡Un componente, una función!
Especializándonos en el patrón Factory
Si analizamos más detenidamente el ultimo snippet con lo que acabamos de decir, nos damos cuenta de que podemos mejorarlo.
Imaginemos que podemos producir varios de tipos de microondas, lavadoras y neveras. Cada uno de estos tipos tendrán un consumo determinado y unas características determinadas.
Lo primero que podríamos pensar como desarrolladores es «no pasa nada, cada objeto puede tener esos atributos y ya les daremos el valor cuando creemos la instancia concreta. ¡Error! Hemos creado el patrón Factory para desacoplar precisamente la creación de los objetos. Queremos que Main tenga en su poder, simplemente llamando al creador concreto, el objeto que necesita.
Por ejemplo, podemos crear dos tipos de electrodomésticos, de gama básica y premium con valores distintos en sus atributos, y podemos hacerlo de dos formas gracias al patrón Factory. Porque en realidad existen dos tipos de patrón Factory.
Patrón Method Factory
El patrón Method Factory consiste en crear una clase por cada objeto que se pueda crear. Por ejemplo, en nuestro caso tendríamos una clase ElectrodomesticoFactory abstracta tantas subclases como productos vendamos, y cada una de ellas crearían el producto de una determinada forma.
Ahora en el Main solo tendremos que preocuparnos de obtener la referencia correcta del tipo de creador de electrodomésticos que queramos.
¿Que hemos conseguido creando la interfaz con el patrón Method Factory? Hemos desacoplado aún más la clase con la que nos comunicamos desde el Main con las implementaciones concretas de los productos, de forma que si creamos nuevos tipos de productos, no tenemos que cambiar nuestra interfaz, solo crear una subclase de esta. Es decir, hemos cumplido con la máxima open-closed.
Patrón Abstract Factory
A diferencia del Method Factory, el patrón Abstract Factory crea una interfaz que agrupa varios métodos constructores pero para objetos diferentes. Veámoslo mejor con un diagrama:
Este patrón por tanto, crea una interfaz con métodos creadores de varios productos y los creadores concretos decidirán cómo se construyen y el tipo concreto a construir. Es lo que parece: una suerte de agrupación de clases concretas del patrón Method Factory.
Las diferencias fundamentales entre los dos patrones
Es normal confundirlos porque hacen algo muy parecido a primera vista y normalmente se pueden usar indistintamente, con dos grandes puntos a tener en cuenta:
- El patrón Abstract Factory podría requerir sobrescribir varias clases si se crea un elemento nuevo. En nuestro caso, si introducimos un lavavajillas, tendríamos que heredar createLavavajillas() en el creador concreto pero implicaría más o menos trabajo en función de nuestra aplicación y de cuántas formas de construir electrodomésticos (o subclases de la factoría abstracta) tengamos. Por el contrario, con el patrón Method Factory, solo tendríamos que crear una nueva clase que extendiera de la factoría abstracta.
- El patrón Method Factory tiene como contrapartida que si existen muchos elementos concretos que se pueden construir, y muchas formas de construirlos, es un método poco práctico de implementar*.
- El patrón Abstract Factory es muy útil para componer objetos. Pasando por ejemplo como argumento al constructor de un objeto una referencia a una factoría concreta, podemos obtener multitud de objetos en tiempo de ejecución, mientras que con el patrón Method Factory, esto sería mucho más engorroso, ya que deberíamos pasar una referencia al mismo constructor por cada objeto que quisiéramos crear.
*Aunque canónicamente se suele utilizar como ejemplo una subclase creadora concreta por producto, es posible (pagando un precio en manteniemiento) parametrizar el método creador. De esta forma podemos obtener varios electrodomésticos en un sólo creador concreto, que podría agrupar varios electrodomésticos según por ejemplo la calidad de su construcción. Sería una suerte de híbrido entre los dos patrones. Esto demuestra que los patrones no son una ley inmutable, sino meras herramientas con pros y contras que valorar.