A menudo, cuando queremos aprender sobre algún patrón de diseño: los ejemplos «sobran». Sin embargo, al menos en mi experiencia personal siempre queda oculta la aplicación práctica del nuevo patrón de diseño estudiado. En este Post se estudiará con un buen caso práctico ilustrado, las bondades del patron de diseño de Fábricas Abstractas.
El contexto de negocios
Supongamos que se quiere diseñar un sistema para el control de procesos de negocios relacionados con la compra y venta y el manejo de transacciones comerciales para importaciones. Para ello se contempla la idea de desarrollar un sistema manejador de órdenes de compra, el cual estará sujeto a las siguientes restricciones:
- Para cada orden de compra se debe considerar dos tasas de impuestos inclusives: la tasa de puerto y la tasa de impuesto general.
- Estos impuestos serán sumados en caso de ser necesario al monto neto de la operación.
- Ambas tasas varían en el contexto de la zona de la importación de la carga.
- Se deben tomar en cuenta configuraciones de leyes para importaciones en América y Europa, así como «la nueva ley de la comunidad Europea» (*1) mediante la cual si una carga es de un artículo electrónico se consideran nuevos cargos de impuestos.
De manera que, la ilustración del proceso de negocio a construir pudiera ser algo como:
En resumen, el objetivo del sistema debe ser el de procesar ordenes de compra acordes a la las leyes impuestas y que este tenga la suficiente flexibilidad como para permitir adaptarse a otras situaciones, continentes, países y leyes.
Las fábricas Abstractas
El patrón de creación de Fábricas Abstractas es un derivado directo del patrón Fábrica pero con otro nivel más agregado de abstracción e indirección. De esta manera, se separa un conjunto de implementaciones de sus definiciones, generando objetos responsables de manejar un contexto específico.
El Abstract Factory se emplea a menudo cuando hay una necesidad de utilizar diferentes conjuntos de objetos y donde los objetos se han añadido o cambiado en algún momento durante la vida útil de una aplicación.
El uso de este patrón agrega valor en el sentido de que se pueden realizar intercambios entre clases concretas sin necesidad de cambiar el código en tiempo de ejecución. Como contra añade un nivel de complejidad al código a la hora de realizar el diseño.
Resolviendo el problema de negocio
Para resolver el problema planteado de procesar órdenes de compra y sus impuestos utilizaremos el patrón Fábrica Abstracta. Lo primero a definir sera que fábricas utilizaremos y que objetos crearemos con ellas:
- Fábrica destino: esta fábrica sera la que usará el cliente para obtener una instancia de la clase de herramienta financiera (que conoce la lógica de negocio) dependiendo del destino (Europa o América). Esta Fábrica devolverá una instancia de fábrica de herramienta financiera.
- Fábrica Herramienta financiera: Es una clase abstracta con dos métodos de creación para el procesador de tax y el procesador de la tasa de puerto. Ambos métodos retornan interfaces con el contrato para calcular los impuestos necesarios en contexto.
- Fábrica de Herramienta Financiera para América: mediante esta fábrica concreta para el continente americano, se obtienen los objetos concretos para procesar el tax y los impuestos de puerto según las normas de este continente.
- Fábrica de Herramienta Financiera para Europa: esta fábrica concreta trabaja de igual forma que la anterior, pero en este caso para el continente europeo con fin de obtener los objetos concretos para procesar el tax y los impuestos de puerto para este continente.
De esta forma, nos aseguramos de separar la lógica del negocio del cliente, quien solo realiza peticiones de clases abstractas y recibe clases concretas, manteniendo de esta forma un bajo acoplamiento y una alta cohesión. Lo siguiente es determinar las interfaces e implementaciones de los objetos que se encargaran de procesar los impuestos:
Finalmente, como se puede observar en el diagrama de clase de diseño, la clase procesar orden agrega la interfaz ProcesadorTax y ProcesadorTasaPuerto para en el momento de ser creada utilice las fábricas y trabaje con los objetos correspondientes al caso.
El código perteneciente a la clase ProcesarOrden es:
public class ProcesadorOrden { private ProcesadorTasaPuerto procesadorTasaPuerto; private ProcesadorTax procesadorTax; private FabricaHerramientaFinanciera fabricaHerrFinanc; public ProcesadorOrden(DestinoOrden destino) { fabricaHerrFinanc = FabricaDestino.obtenerFabricaPara(destino); procesadorTasaPuerto = fabricaHerrFinanc.crearProcesadorTasaPuerto(); procesadorTax = fabricaHerrFinanc.crearProcesadorTax(); } public void procesarOrden(Orden orden) { Double montoTotal = orden.getMontoNeto(); Double subTotalTax, subTotalPuertos; subTotalTax = procesadorTax.calcularTax(orden); subTotalPuertos = procesadorTasaPuerto.CalcularTasaPuerto(orden); montoTotal += subTotalTax + subTotalPuertos; System.out.println("--------------------------------------"); System.out.println(" ORDEN PROCESADA"); System.out.println("--------------------------------------"); System.out.println("Id: " + orden.getIdOrden()); System.out.println("Cliente: " + orden.getNombreCliente()); System.out.println("Monto Neto: " + orden.getMontoNeto() + " $"); System.out.println("Tasa de Puertos: " + subTotalPuertos + " $"); System.out.println("TAX: " + subTotalTax + " $"); System.out.println("Monto Total (mas impuestos): " + montoTotal + " $"); } }
Descargar código completo en Java
Análisis y Conclusiones
De esta manera hemos realizado el diseño de un sistema bastante robusto para soportar diferentes lógicas de negocio en tiempo de ejecución. Si por ejemplo en el futuro hubiese que agregar soporte para manejo de impuestos en el continente Asiático, solo tendríamos que incorporar la fábrica perteneciente a la herramienta financiera, las clases concretas que implementen a la interfaz y así, con esta separación de responsabilidades se mantiene mas seguro el código en el tiempo, agregando valor al negocio y mejorando el retorno de la inversión. Por supuesto, con esta técnica habría que recompilar el código antes de poner las nuevas funcionalidades en producción, pero existen otras técnicas como la inyección de dependencias, soportado por ejemplo por el marco de trabajo Spring, que nos aporta mucho valor para estas circunstancias.
(1*) La ley de la comunidad Europea es un ejemplo que no refleja la realidad, utilizado para ilustrar este contexto de negocio.
Referencias
Benneth Christiansson et al. GoF Design Patterns – with examples using Java and UML2. 2008 Creative Commons Attribution-ShareAlike 3.0 License