En agosto de 1994 se editó un libro que inició un cambio en la
forma de entender y trabajar en arquitectura de software,
estableciendo unas bases que se han ido asentando durante estos ya
más de diez años transcurridos desde su publicación. El título del
libro es "Design Patterns: Elements of Reusable Object-Oriented
Software" (traducido y publicado en español por la editorial
Pearson en 2003), de los autores Erich Gamma, Richard Helm, Ralph
Jonson y John Vlissides. Este equipo de autores también es conocido
como el "Gang of Four" o GoF, literalmente, "la banda de los
cuatro".
Puede afirmarse que este libro, basado en la tesis doctoral de
Erich Gamma, ha revolucionado la arquitectura de software y llevado
el diseño orientado a objetos a un nivel más alto. Si bien es
cierto que, como la gran mayoría de inventores, el creador real del
concepto de patrones permaneció relativamente en el olvido. La
persona que hay detrás de los patrones de diseño es el arquitecto
Christopher Alexander, que durante 1970 escribió dos libros, "A
Pattern Language" y "A Timeless Way of Building", que además de
ofrecer ejemplos describió sus razonamientos para documentar los
patrones. Desde entonces, el movimiento pro-patrones estuvo muy
quieto hasta 1987, momento en el que volvieron a resurgir, teniendo
como resultado la tesis doctoral de Erich Gamma (1991) y
finalmente, la publicación del libro citado inicialmente, que
inicio el fenómeno de los patrones de diseño que actualmente
conocemos. Para que nos hagamos una idea de la importancia de la
labor de Alexander, podemos afirmar que ésta es similar a la de
Noam Chomsky en el área de los lenguajes computacionales, pero
aplicada a la arquitectura de software (más información en
http://es.wikipedia.org/wiki/
Christopher_Alexander).
"El lenguaje de patrones brinda a todo el que lo utilice el poder
de crear una infinita variedad de construcciones nuevas y únicas,
de la misma forma que su lenguaje común le brinda el poder de crear
una infinita variedad de oraciones." Christopher Alexander.
Patrones de diseño: ¿por qué?, ¿para qué? y ¿cómo?
¿Por qué?
Seguramente os estaréis preguntando por qué deberíamos darle a este
tema importancia o incluso nuestro tiempo, un bien tan escaso hoy
en día, en el que aparecen nuevas tecnologías tan fascinantes como
complejas y casi de estudio requerido... en el que los proyectos se
nos acumulan, se complican y requieren que dediquemos horas extra,
tanto en el trabajo como fuera de él, para auto-formarnos y poder
resolver el problema que nos ha dejado "bloqueados" hoy al día
siguiente… con el consabido y ya asumido problema de la falta de
tiempo (y en algunos casos, de sueño).
La respuesta la podríamos resumir en una simple frase: "porque
alguien ya ha resuelto tu problema". Así de simple, así de fácil…
Además, las soluciones están escritas, catalogadas y
clasificadas.
Básicamente, los patrones de diseño son el conjunto de experiencias
de un gran número de desarrolladores y arquitectos de software,
clasificados según los problemas más frecuentes en el diseño de
software. Y además, nos encanta la reusabilidad, ¿no? Todos
llevamos un gran vago dentro y nos encanta solucionar un problema o
proponer una solución con el menor esfuerzo posible. Y si luego de
resolverlo vemos que encima es la solución más optima en
flexibilidad, adaptabilidad y con el menor acoplamiento posible,
mejor aún, ¿no?
Como resumen de lo dicho, he aquí otro párrafo de Christopher
Alexander, citado en el ya mencionado libro del GoF, en la sección
1.1: "¿Qué es un patrón de diseño? Cada patrón describe un problema
que ocurre una y otra vez en nuestro entorno, así como la solución
a ese problema, de tal modo que se pueda aplicar esta solución un
millón de veces, sin hacer lo mismo dos veces."
¿Para qué?
Bueno, si esto no os convence y sois de la opinión "bueno, vale,
los patrones pueden ayudarme, pero ¿para qué quiero aprenderlos si
no me los van a valorar?". Básicamente, la respuesta vuelve a ser
una frase: "para no reinventar la rueda, para aprovechar por una
vía menos dura que la experiencia propia la experiencia de los
demás…".
Para proveer a nuestros clientes de soluciones probadas y
consistentes, además de flexibles. Sí, en cierto modo me repito,
pero es importante tener esto claro, ya que muchas veces quizás no
nos vayan a apreciar el realizar un diseño de software adecuado,
excepto cuando tengamos que modificarlo nosotros mismos o algún
compañero, en cuyo caso estaremos haciéndonos un favor, así como a
nuestros compañeros, dado que no saldrá la tan típica expresión
"esto es un bodrio… quien haya hecho esto...", cosa que,
profesionalmente, nos conviene… También pueden decir "esto es
realmente complejo, no lo entiendo", lo cual también nos
compensará, dado que solo con hacer la pregunta "¿te suena el
concepto de patrones de diseño?" nos habremos posicionado como
arquitectos de software.
Con la aplicación de los patrones, estaremos proveyendo a nuestros
diseños de software de un lenguaje común que otros arquitectos
podrán comprender y mediante el cual podremos dialogar con ellos.
También nos mejorará como desarrolladores, o mejor dicho, como los
arquitectos de software que pretendemos ser.
Podría citar muchas otras razones de porqué utilizar patrones de
diseño, pero todas se reducen a una muy clara y simple, que nos
sonará si hemos trabajado algún tiempo con diseño orientado a
objetos: evitar los errores (o dudas) más comunes de la POO
(Programación Orientada a Objetos); la programación orientada a
objetos está aún en pañales, y mi experiencia con otros
profesionales me indica que no se le da la importancia que merece,
tanto en centros de formación como en universidades, de las cuales
la gente sale sin un conocimiento de la programación "real", con
mucha confusión sobre el desarrollo orientado a objetos, quedando
éste muy borroso, y con la sensación -muy real- de que solo es
aprendido mediante la experiencia; algo costoso y doloroso, en
muchos casos.
Para verlo algo más claro, ¿qué mejor que un ejemplo?
Un error muy común, por ejemplo, es la "muerte por herencia". Si
tenemos una aplicación que debe acceder a un objeto de un
determinado tipo (el tipo es de una "clase base"), teniendo la
arquitectura que se muestra en la figura 1, entonces el
desarrollador tiene (si no ha declarado la clase base como privada
en la clase derivada) acceso completo a los métodos de la clase
base, y la clase derivada debe asegurarse de implementar todas las
funcionalidades de la clase base.
Lo cual, plasmado en código, queda según se muestra en la figura 2
y el fuente 1.
Esto nos parece realmente correcto, pero en la realidad Murphy
existe y sus leyes se cumplen… Podemos perder el control de la
clase base, quedando ésta bajo el control de otro desarrollador
quizás menos comunicativo que nosotros y que "protege su trabajo",
con lo que no tenemos opción o elección sobre la implementación de
la clase base, pudiendo tener destructores no virtuales, o métodos
que no han sido declarados como virtuales, y miembros privados...
Todo ello nos determina de una forma drástica y nos hace imposible
especializar un objeto vía derivación.
Otra preocupación sobre la programación "por herencia" es el
crecimiento de una estructura de herencia rígida, pudiendo aparecer
problemas en lo que al principio parecía la solución ideal.
Básicamente, las clases base pueden destrozar una aplicación con
tan solo cambiar ligeramente su implementación (figura 3).
Para solucionar este problema, tenemos el patrón de diseño Façade
(fachada), que nos "proporciona una interfaz unificada para un
conjunto de interfaces de un subsistema", según el libro "Patrones
de diseño". Diciéndolo de una forma más comprensible, nos indica
que utilicemos la encapsulación para implementar un wrapper
(envoltorio) alrededor de otros objetos, algo que con la simple
herencia no podemos hacer. Con ello tendríamos lo siguiente (figura
4).
Esto, expresado en código, quedaría como se muestra en el fuente
2.
Es decir, no deberemos modificar la aplicación para que continúe
funcionando; solamente deberíamos adaptar la clase externa que hace
las veces de fachada, con lo que tendríamos una mejor solución que
simplemente aplicando la herencia, obteniendo a la vez con
ello un mantenimiento mucho menos costoso.
Como hemos podido ver, las ventajas de aplicar los patrones de
diseño son claramente evidentes, no solo en lo que respecta a la
calidad del código o a su implícita reducción de costes, sino
también en cuanto a acceso, soporte, diseño, extensibilidad y
flexibilidad.
Conceptos de los patrones de diseño
Es importante, antes de ver los diferentes patrones existentes y su
clasificación, analizar las características comunes entre ellos y
los conceptos que comparten.
El más importante de todos es que los patrones no son
implementaciones de código para un problema concreto. No es de
código de lo que tratan los patrones, sino de conceptos e
interacción entre ellos, proponiendo soluciones conceptuales que
pueden tener muy diversas formas de implementación; cada cual
deberá adaptarlos a su problema y arquitectura concretos.
Los patrones de diseño básicamente, soluciones conceptuales que
tratan sobre el diseño e interacción entre objetos y proporcionan
soluciones reutilizables para resolver los problemas más comunes.
Se representan normalmente mediante la ayuda de un diagrama
UML.
Existen unos principios fundamentales de diseño, que son
subyacentes a los patrones de diseño, siendo el principio para la
creación de soluciones flexibles:
• Programar para interfaces y no para una
implementación (objetos).
• Potenciar la composición de objetos frente la
herencia de clases.
Derivados de éstos, tenemos otros dos principios no menos
importantes:
• Determinar qué es común y qué es
variable.
• Encapsular las secciones identificadas como
variables mediante una interfaz común.
Por ejemplo, si definimos la interfaz IPerro y una clase
cPastorAleman que la implemente (figura 5 y fuente 3).
Esto sería programar para una implementación:
Dim Carla As cPastorAleman =
New cPastorAleman
Carla.Ladra()
Declarar la variable Carla del tipo cPastorAleman, una
implementación concreta de IPerro, nos fuerza a codificar sobre una
implementación concreta.
Pero programar para una interfaz o supertipo, por ejemplo:
Dim Carla As IPerro =
New cPastorAleman
Carla.Ladra()
Es mucho mejor, ya que sabemos que Carla es un pastor animal, pero
podemos utilizar la referencia a IPerro polimórficamente, con lo
cual en lugar de "fijar" en el código la instanciación de un
subtipo, podríamos asignar la implementación concreta en tiempo de
ejecución, haciendo aún más flexible nuestro código. Es más,
podemos añadir diferentes tipos de perro a nuestra aplicación y
ésta seguirá trabajando con ellos sin ningún cambio, mientras que
de la otra forma, deberíamos programar código para cada nueva
raza.
Cabe destacar que los conceptos o principios que hemos visto,
además de ser las bases de los patrones de diseño, son las bases de
un correcto diseño orientado a objetos. Los patrones de diseño se
consideran como la evolución de estos mismos conceptos, por lo que
es muy importante entenderlos bien y tenerlos en mente.
Clasificación de los patrones de diseño
Hay tres tipos de patrones de diseño:
• Creacionales: abstraen el proceso de creación
de instancias de objetos, ayudando a hacer un sistema que sea
independiente de los procesos de creación, composición y
representación de los mismos.
• Estructurales: enfocan los problemas de
composición, estructuración y herencia de clases y objetos, así
como su uso para componer estructuras de mayor tamaño.
• De comportamiento: son aquellos patrones que se
centran en mejorar el modo en que las clases y objetos interactúan
y se comunican entre ellos, repartiendo su responsabilidad.
Actualmente hay identificados 23 patrones de diseño, que se listan
en la tabla 1. No entraremos en detalles aquí, ya que citarlos sin
entrar en su "salsa" es como insultarlos; cada uno de ellos se
merece un artículo entero para él, así son de importantes y
potentes…
¿Cómo y cuándo utilizarlos? (y cuándo no)
Aquí no se trata de reutilizar código, sino experiencia. Los
patrones de diseño son conceptos y organización de los mismos que
se transfieren fácilmente a código; pero lo que nos tiene que
quedar claro son los conceptos y no el código (ojo: también ayuda,
pero uno sin lo otro no tiene sentido). Si sólo aprendemos el
código estaremos aprendiendo solamente el truco y no la técnica
subyacente, que es lo que realmente importa. Si aprendemos los
conceptos subyacentes, evolucionamos como arquitectos de software y
aumentamos realmente nuestras armas de comprensión y solventación
de problemas.
Para saber usar los patrones de diseño hay que conocerlos, saber
reconocerlos, entenderlos y comprender sus ventajas y beneficios,
para luego aprender a implementarlos. Un ejercicio muy bueno
consiste en identificar los patrones de diseño en nuestro día a
día, tanto en .NET Framework como en la "vida real". Por ejemplo,
un patrón de diseño de tipo façade lo podemos hallar en nuestro
reproductor MP3, en el cual hay un wrapper que encapsula la
complejidad interna y nos proporciona los métodos play, stop,
pause, etc. Otro muy buen ejercicio es tratar de encontrar estos
patrones dentro de nuestro código; esto nos desvinculará de la
complejidad de abstraer estos conceptos en un entorno ya abstracto
de por sí como son las líneas de código.
Por último, también hay que tener en cuenta que pese a ser unas
estupendas herramientas, no son "la técnica" definitiva ni la
solución a todo. No hay que caer en la definición de desarrollador
pattern happy, que a todo lo que toca intenta aplicarle cuanto más
patrones, mejor. Hay momentos en los que aplicar patrones y también
los hay para no hacerlo.
Referencias
Recomiendo sinceramente el libro de referencia que inició este
cambio en el campo de la arquitectura de software: "Patrones de
diseño: elementos de software orientado a objetos reutilizable", de
Erich Gamma, Richard Helm, Ralph Jonson y John Vlissides
(Pearson/Addison Wesley). Igualmente recomiendo el libro "Head
First Design Patterns", de Elisabeth Freeman y Eric Freeman
(editorial O'Reilly), que recientemente descubrí mientras me
documentaba para este artículo y cuya exposición de los patrones de
diseño es realmente magnífica.