martes, 22 de mayo de 2012

Patrones de diseño: implementando Abstract Factory

Los patrones molan, si sabes cómo usarlos, más.

Hace algún tiempo compramos en la oficina el libro “Patrones de diseño para C#”. Este libro detalla los 23 modelos de diseño fundamentales y aunque aún no he tenido tiempo de leerlo al completo, lo que he visto está bien explicado y es sencillo de entender.

No es mi intención debatir los beneficios que se obtiene al utilizar patrones de diseño, de eso seguro que puedes encontrar muchísima información en miles de blogs.

Sin embargo, si lo que buscas son ejemplos de cómo (pero sobretodo porqué) implementar un patrón de diseño desde cero, ahí amigo, seguro que lo tienes más difícil.

Los ejemplos que yo he encontrado, muchas veces son excesivamente formales, y se centran en explicar el patrón en sí, implementando algún ejemplo concreto sobre la definición formal del patrón.

Mi idea es partir de un escenario en el que el código es correcto, en términos funcionales, pero no es correcto en el sentido de que incumple varios principios SOLID, y además en el momento en el que las especificaciones cambien, va a ser también más complicado de mantener.

Veremos que para optimizar el escenario propuesto, el patrón que mejor encaja es el denominado “Factoría Abstracta”. Trataré de explicar porqué, y reescribiremos el código hasta tener la implementación del patrón completada.

De esta manera, podremos evaluar los beneficios que vamos a obtener al usar la Factoría Abstracta, nuestro código ganará calidad, nosotros algo de ego (no mintáis, malditos geeks!), a partir del momento en el que decidáis incorporar el uso de patrones a vuestros proyectos, miles de gatitos dormirán tranquilos… y a nosotros no nos “sangrarán” los ojos al ver nuestro ahora precioso código.

Bueno, al turrón! y ánimo, que me ha salido un “troncho” de post que no es para unas prisas ;-D

Se abre el telón.

Y nos encontramos con una aplicación que envía mails. Dichos mails están formados por diferentes secciones predefinidas. Por ejemplo, el mail que envía información de un pedido está formado por:

  • Una sección de cabecera del pedido
  • Las líneas del pedido
  • Información del cliente
  • Información de la visita realizada

De esta manera, en función de las distintas secciones existentes, el usuario puede formar los mails con la información que desea, y en tiempo de ejecución el mail renderiza su contenido según las secciones que lo forman, y se queda listo para enviar. Vamos a ver la estructura del proyecto:

Tenemos un enumerado para las distintas secciones existentes:

1 Public Enum MailSectionType As Integer
2 CustomerInfo
3 OrderInfo
4 OrderLines
5 VisitInfo
6 End Enum


La clase que representa la sección del mail:





Ahora, la clase que representa el mail:





La clase que forma los mails, y que es precisamente la que vamos a modificar. Fijaos en la función GetMailSectionText y ese Select Case tan “bonico”.







Y por último, un modulo que pone todo esto en marcha.





Si ejecutamos el proyecto, veremos que para cada definición de mail, el programa pinta correctamente su contenido, presentando cada una de las secciones que lo forman. Entonces… ¿qué es lo que no es correcto? ¿dónde podemos mejorar nuestro código?



Vamos a por ello, amiguitos…



Maldita sea! pero si esto funciona!!



Efectivamente y no!



Imagina que el método GetMailSectionText de la clase MailManager, realiza para cada Case un acceso a datos distinto y  formatea cada sección de manera diferente, para finalmente retornarla.



En ese caso, vemos que nos estamos saltando uno de los principios SOLID, concretamente el principio de responsabilidad única, (SRP: Single Responsibility Principle).



Dicho principio establece que cada clase debe tener una única responsabilidad, y que esa responsabilidad debe estar completamente encapsulado por la clase.



Como vemos, la clase MailManager, y concretamente su función GetMailSectionText hace bastantes más cosas de las que debería



Imagina además que el número de secciones no es de cuatro como en el ejemplo, sino mucho mayor, y que para añadir más leña al fuego es muy probable que esta colección de secciones aumente con el tiempo.



Entonces merece la pena darle una buena pensada a ese código, e implementar una solución que encapsule correctamente las responsabilidades que hemos encontrado, que sea tolerante a las modificaciones, y que en caso de que éstas se produzcan nos permita extenderlas fácilmente.



El maestro Robert C. Martin, en su magnifico libro Clean Code (¿No lo has leído? estás tardando!) comenta que no puede con los bloques Switch (el equivalente al Select Case de java), y que sólo los tolera cuando aparecen como entrada a una implementación de Abstrac Factory (ya pensabas que no aparecería, ehhh… jeje), para devolver objetos polimórficos pertenecientes a una misma familia (lo cualo??).



No vamos a ver el modelo formal estático de Abstract Factory, puedes verlo detalladamente aqui.



Pero si que vamos a realizar el modelo estático para nuestro ejemplo. A por ello!



Deconstruyendo Abstract Factory (Ferrán Adriá Style)



Lo primero que vamos a hacer es identificar dentro de nuestro código, los elementos del patrón, y detallar las conversiones que vamos a realizar. El patrón Factoría Abstracta tiene los siguientes elementos:





  • Factoría abstracta

  • Factorías concretas

  • Producto abstracto

  • Productos concretos


El patrón Factoría permite crear objetos complejos, ocultando el proceso de creación a las clases cliente. Además, dichos objetos complejos pertenecen a una misma familia



En nuestro caso, los objetos “complejos” a crear son de la clase MailSection. Lo que vamos a hacer es crear una clase abstracta MailSection, de la que heredarán los siguientes objetos concretos:





  • CustomerInfoMailSection

  • OrderInfoMailSection

  • OrderLinesMailSection

  • VisitInfoMailSection


Nuestra clase “ProductoAbstracto” es la siguiente:





Y las clases “Producto Concreto” las siguientes:





Pero… ¿y las factorías? Pues vamos a extraer una factoría concreta de cada una de las secciones de nuestro select case de la función GetMailSectionText, de esta manera:





Y como nuestro patrón necesita de una factoría abstracta, vamos a obtener esta por generalización de las anteriores. Aquí la tenemos!





Vamos a ver el resto de factorías:





Como vemos, cada factoría retorna un producto abstracto, pero internamente utiliza su producto concreto “asociado”, para la creación. La magia del polimorfismo, amigos!!



Aquí podemos ver nuestro modelo estático al completo



Y ahora… tachaaan! vamos a modificar la clase MailManager para utilizar todo lo que hemos creado.



La esencia es eliminar el código del Select Case, y modificar la función ComposeMail para utilizar nuestro flamante método GetMailSectionFactory (polimórfico tú, polimórfico yo…). De esta manera, la función utiliza la factoría acorde al tipo solicitado, que es capaz de crear el objeto MailSection concreto solicitado.



Sin embargo, hacia fuera el cliente sólo trata con los objetos abstractos MailSectionFactory y MailSection, quedando toda la complejidad de la creación de objetos encapsulada en las factorías.



Finalmente queda así:





Igualita que antes, a que si!!



En el cliente, podemos poner todo esto en marcha de nuevo:





Y si ejecutamos, veremos los mails creados, primero el de pedido y luego el de cliente:



image



image



Objetivo conseguido! tenemos nuestra flamante factoría a pleno rendimiento Guiño



Recapituling, my friend



 


Enumerar beneficios para el ejemplo:



  • Potenciamos el principio SRP
  • Separamos el código de creación del código que utiliza dichos objetos
  • Mejora la ingeniería del software, el uso de patrones de diseño eleva el nivel del equipo de desarrollo
  • Mejora la calidad y estructura
  • Reducimos el acoplamiento entre objetos

Enlace a http://geeks.ms/blogs/gperez/archive/2009/08/24/patrones-de-dise-241-o-screencast-capitulo-2-patr-243-n-factory.aspx Patrón factoría de Chalalo


Pasos para incluir una nueva sección:



  • Implementar una nueva clase que herede de MailSection, y que será nuestro nuevo producto concreto.
  • Implementar una nueva factoría concreta, que heredará de MailSectionFactory. Esta nueva factoría creará objetos de la nueva MailSection

3 comentarios:

  1. Muchas gracias por el ejemplo :) Sólo un consejillo: las capturas de pantalla no muestra el código completo. Podrías ponerlo para descargarlo.

    ResponderEliminar
    Respuestas
    1. Gracias por tu comentario!
      Antes usaba un plugin para que se viese correctamente el código, no recuerdo porqué este post no lo hice así.

      Intentaré subir el código lo antes posible.

      Un saludo!

      Eliminar
  2. Este comentario ha sido eliminado por un administrador del blog.

    ResponderEliminar