Introducción a los Patrones de Diseño

Historia de los Patrones de Diseño

El término patrón fue utilizado por primera vez por el arquitecto Christopher Alexander en el libro A Pattern Language: Towns, Buildings, Construction, donde definió una serie de patrones arquitectónicos. Alexander define:

“Un patrón describe un problema que ocurre a menudo, acompañado por un intento de solución para el problema.”

Christopher Alexander, 1977

En 1987, Ward Cunningham y Kent Beck estaban trabajando con Smaltalk, diseñando interfaces de usuario. Para ello, decidieron utilizar alguna de las ideas de Alexander y desarrollaron un pequeño lenguaje de patrones que serviría de guía a los programadores de Smaltalk. A partir de estas idea escribieron el libro Using Pattern Languajes for Object-Oriented Programs.

Desde 1990 a 1994, Erich Gamma, Richard Helm, Ralph Johnson y John Vlissides (Gang of four) realizaron un primer catálogo de patrones de diseño. En 1994 publicarón el libro Design Patterns – Elements of Reusable Object-Oriented Software que introducía el termino de patrón de diseño en el desarrollo del software.

¿Qué son los patrones de Diseño?

Un patrón de diseño es una solución repetible a problemas típicos y recurrentes en el diseño del software. Son soluciones basadas en la experiencia y que se ha demostrado que funcionan. No son un diseño terminado que puede traducirse directamente a código, sino más bien una descripción sobre cómo resolver el problema, la cual puede ser utilizada en diversas situaciones. Eric Gamma define los patrones de diseño como:

“Un patrón de diseño es una descripción de clases y objetos comunicándose entre sí para resolver un problema de diseño general en un contexto particular.”

En general, un patrón de diseño consta de cuatro elementos esenciales:

  1. El nombre del patrón se utiliza para describir un problema de diseño, su solución y consecuencias en una o dos palabras. Nombrar un patrón incrementa inmediatamente nuestro vocabulario de diseño. Esto nos permite diseños a un alto nivel de abstracción. Tener un vocabulario de patrones nos permite hablar sobre ellos con nuestros compañeros, en nuestra documentación, e incluso a nosotros mismos.
  2. El problema describe cuando aplicar el patrón. Se explica el problema y su contexto. Esto podría describir problemas de diseño específicos tales como algoritmos como objetos. Podría describir estructuras de clases o objetos que son sintomáticas de un diseño inflexible. Algunas veces el problema incluirá una lista de condiciones que deben cumplirse para poder aplicar el patrón.
  3. La solución describe los elementos que forma el diseño, sus relaciones, responsabilidades y colaboraciones. La solución no describe un diseño particular o implementación, porque un patrón es como una plantilla que puede ser aplicada en diferentes situaciones. En cambio, los patrones proveen una descripción abstracta de un problema de diseño y como una disposición general de los elementos (clases y objetos en nuestro caso) lo soluciona.
  4. Las consecuencias son los resultados de aplicar el patrón. Estas son muy importantes para la evaluación de diseños alternativos y para comprender los costes y beneficios de la aplicación del patrón

Definición de los Patrones de Diseño

La documentación de un patrón de diseño describe el contexto en el que se utiliza el patrón, las fuerzas dentro del contexto en el que el patrón busca resolver, y la solución sugerida. No existe un formato único para la documentación de los patrones de diseño. El formato más frecuentemente utilizado es el utilizado por GoF en su libro de Patrones de Diseño. Contiene las siguientes secciones:

  • Nombre del patrón: identifica al patrón.
  • Clasificación del patrón: creacional, estructural o de comportamiento.
  • Intención: ¿Qué problema pretende resolver el patrón?
  • También conocido como: Otro nombres por los que es conocido el patrón.
  • Motivación: Escenario de ejemplo para la aplicación del patrón.
  • Aplicabilidad: Usos comunes y criterios de aplicabilidad del patrón.
  • Estructura: Diagramas de clases oportunos para describir las clases que intervienen en el patrón.
  • Participantes: Enumeración y descripción de las entidades abstractas (y sus roles) que participan en el patrón.
  • Colaboraciones: Explicación de las interrelaciones que se dan entre los participantes.
  • Consecuencias: Consecuencias positivas y negativas en el diseño derivado de la aplicación del patrón.
  • Implementación: Técnicas o comentarios oportunos de cara a la implementación del patrón.
  • Código de ejemplo: Código fuente de ejemplo de la implementación del patrón.
  • Usos conocidos: Ejemplos de sistemas reales que usan el patrón.
  • Patrones relacionados: Referencias cruzadas con otros patrones.

Listado Resumen de Patrones de Diseño

Existe una gran cantidad de patrones de diseño y tampoco pretendo explicarlos todos aquí. He escogido un pequeño listado de los principales patrones de diseño, entre ellos se encuentran los 23 patrones descritos en el libro de “Design Patterns – Elements of Reusable Object-Oriented Software”, además he añadido un listado con patrones de concurrencia y también algunos patrones que me han parecido interesantes.

Patrones Creacionales

Los patrones creacionales ayudan a resolver los problemas relacionados con la creación de instancias de un clase, y así separar la implementación del cliente de la de los objetos que se utilizan. Nos ayudan a abstraer y encapsular su creación.

  • Singleton: Asegura que una determinada clase sea instanciada una y sólo una vez, proporcionando un único punto de acceso global a ella.
  • Abstract Factory: Provee una interfaz para crear familias de objetos relacionados o que dependen entre si, sin especificar sus clases concretas.
  • Builder: Separa la construcción de un objeto complejo de su representación, de forma que el mismo proceso de construcción pueda crear diferentes representaciones. Simplifica la construcción de objetos con estructura interna compleja y permite la construcción de objetos paso a paso.
  • Factory Method: Define una interfaz para crear un objeto, pero deja que sean las subclases quienes decidan qué clase instanciar. Permite que una clase delegue en sus subclases la creación de objetos.
  • Object Pool: Administra la reutilización de objetos cuando un tipo de objetos es caro de crear o solamente un número limitado de objetos puede ser creado.
  • Prototype: Facilita la creación dinámica de objetos mediante la definición de clases cuyos objetos pueden crear duplicados de si mismos. Estos objetos son llamados prototipos.
  • Lazy initialization: Proporciona un camino para retrasar la creación de un objeto o cualquier otro proceso que sea costoso hasta que se necesita por primera vez.
  • Multiton: Expande en el concepto Singleton para gestionar un mapa de instancias de clases nombradas como pares clave-valor, y proporciona un punto de acceso a ellos.

Patrones Estructurales

Utilizados para crear clases u objetos que incluidos dentro de estructuras más complejas.

  • Adapter: Convierte la interfaz de una clase en otra distinta que es la que esperan los clientes. Permiten que cooperen clases que de otra manera no podrían por tener interfaces incompatibles.
  • Bridge: Desvincula una abstracción de su implementación, de manera que ambas puedan variar de forma independiente.
  • Composite: Combina objetos en estructuras de árbol para representar jerarquías de parte-todo. Permite que los clientes traten de manera uniforme a los objetos individuales y a los compuestos.
  • Decorator: Añade dinámicamente nuevas responsabilidades a un objeto, proporcionando una alternativa flexible a la herencia para extender la funcionalidad.
  • Facade: Proporciona una interfaz unificada para un conjunto de interfaces de un subsistema. Define una interfaz de alto nivel que hace que el subsistema se más fácil de usar.
  • Flyweight: Usa el compartimiento para permitir un gran número de objetos de grano fino de forma eficiente.
  • Front controller: Proporciona un punto de entrada centralizado para tramitar las solicitudes. Este patrón se refiere al diseño de las aplicaciones Web.
  • Module: Implementa el concepto de módulos de software, que se define por la programación modular, en un lenguaje de programación que no es compatible o en parte sólo admite módulos.
  • Proxy: Proporciona un sustituto o representante de otro objeto para controlar el acceso a éste.
  • Twin: Permite modelar la herencia múltiple en los lenguajes de programación que no soportan esta función.

Patrones de Comportamiento

Se utilizan a la hora de definir como las clases y objetos interaccionan entre ellos.

  • Blackboard: Se trata de una generalización del patrón Observer que es compatible con múltiples lectores y escritores. El patrón de la pizarra se comunica la información de todo el sistema.
  • Chain of Responsibility: Evita acoplar el emisor de una petición a su receptor, al dar a más de un objeto la posibilidad de responder a la petición. Crea una cadena con los objetos receptores y pasa la petición a través de la cadena hasta que esta sea tratada por algún objeto.
  • Command: Encapsula una petición en un objeto, permitiendo así parametrizar a los clientes con distintas peticiones, encolar o llevar un registro de las peticiones y poder deshacer la operaciones.
  • Interpreter: Dado un lenguaje, define una representación de su gramática junto con un intérprete que usa dicha representación para interpretar las sentencias del lenguaje.
  • Iterator: Proporciona un modo de acceder secuencialmente a los elementos de un objeto agregado sin exponer su representación interna.
  • Mediator: Define un objeto que encapsula cómo interactúan un conjunto de objetos. Promueve un bajo acoplamiento al evitar que los objetos se refieran unos a otros explícitamente, y permite variar la interacción entre ellos de forma independiente.
  • Memento: Representa y externaliza el estado interno de un objeto sin violar la encapsulación, de forma que éste puede volver a dicho estado más tarde.
  • Null Object: En lugar de utilizar null, el patrón Null Object utiliza una referencia a un objeto que no hace nada.
  • Observer: Define una dependencia de uno-a-muchos entre objetos, de forma que cuando un objeto cambia de estado se notifica y actualizan automáticamente todos los objetos.
  • Servant: Define la funcionalidad para un grupo de clases sin definir esa funcionalidad en cada una de esas clases. Un siervo es una clase cuya instancia (o la propia clase) proporciona métodos que realizan un servicio deseado, mientras que los objetos para los cuales (o con quién) el siervo hace algo se pasan a los métodos criado como parámetros.
  • Specification: Recombina las reglas de negocio por el encadenamiento de las reglas de negocio en conjunto usando la lógica de Boole.
  • State: Permite que un objeto modifique su comportamiento cada vez que cambia su estado interno. Parecerá que cambia la clase del objeto.
  • Strategy: Define una familia de algoritmos, encapsula uno de ellos y los hace intercambiables. Permite que un algoritmo varíe independientemente de los clientes que lo usan.
  • Template Method: Define en una operación el esqueleto de un algoritmo, delegando en las subclases algunos de sus pasos. Permite que las subclases redefinan ciertos pasos del algoritmo sin cambiar su estructura.
  • Visitor: Representa una operación sobre los elementos de una estructura de objetos. Permite definir una nueva operación sin cambiar las clases de los elementos sobre los que opera.

Patrones de Concurrencia

Por último, un modelo de concurrencia aborda algunos aspectos de la programación multihilo. Un patrón bien conocido en esta categoría es productor-consumidor, en el que un hilo productor almacena un elemento en un tampón compartida y un hilo consumidor recupera este artículo. El hilo productor no debe almacenar otro elemento en este tampón hasta que el punto anterior se ha consumido, y el hilo consumidor no debe consumir un elemento inexistente.

  • Active object: Desacopla la ejecución de un método desde la invocación del métodos por los objetos en el que cada objeto reside en su propio thread. El objetivo es introducir la concurrencia, mediante el uso de invocación del método asíncrono y un programador para tramitar las solicitudes.
  • Balking: Sólo ejecuta una acción en un objeto cuando el objeto está en un estado en particular. Por ejemplo, un objeto lee un archivo y proporciona métodos para acceder al contenido del archivo. Cuando el archivo no está abierto y se intenta llamar a un método para acceder al contenido, el objeto “rechaza” la petición.
  • Binding properties: Combina varios observadores para forzar propiedades en diferentes objetos a ser sincronizados o coordinados de alguna manera.
  • Double-checked locking: Reduce la sobrecarga de adquirir un bloqueo probando primero el criterio de bloqueo (la “sugerencia de bloqueo”) sin llegar a la adquisición del bloqueo. El bloqueo sólo se adquiere cuando el chequeo del criterio de bloqueo indica que se requiere de un bloqueo.
  • Event-based asynchronous: Un patrón de concurrencia para la invocación asíncrona de métodos potenciales larga ejecución de un objeto.
  • Guarded suspension: Gestiona las operaciones que requieren tanto un bloqueo para ser adquirida y una condición previa para ser satisfecho antes de la operación puede ser ejecutada.
  • Lock: Un mecanismo para hacer temporalmente algún aspecto de un objeto o inmodificable para suprimir las notificaciones de actualización que no sean necesarios.
  • Messaging design pattern (MDP): Permite el intercambio de información (es decir, los mensajes) entre los componentes y aplicaciones.
  • Monitor object: Un objeto cuyos métodos están sujetos a la exclusión mutua, evitando así varios objetos de forma errónea tratando de utilizar al mismo tiempo.
  • Reactor: Un modelo de gestión de eventos para el manejo de solicitudes de servicio entregados simultáneamente a un manejador de servicio por una o más entradas. El controlador de servicio, entonces desmultiplexa las solicitudes entrantes y los envía de forma sincrónica a los gestores dependientes asociados.
  • Read-write lock: Un bloqueo que permite el acceso de lectura simultáneo a un objeto, sino que requiere acceso exclusivo para las operaciones de escritura.
  • Scheduler: Un patrón de concurrencia utilizado para controlar de forma explícita cuando las discusiones pueden ejecutar código de un único subproceso; por ejemplo, varios subprocesos con ganas de escribir datos en el mismo archivo.
  • Thread pool: Un patrón de concurrencia en el que se crean un número de hilos para realizar diversas tareas, que se organizan por lo general en una cola.
  • Thread-specific storage: Un patrón de concurrencia en el que la memoria estática o global se localiza en un hilo. Cada hilo tiene su propia copia de la presente memoria.

Seleccionar un patrón de diseño

De entre los diferentes patrones de diseño que existen puede ser complicado la elección de uno para el diseño de una aplicación. Entre las pautas que hay que seguir a la hora de seleccionar un patrón se pueden indicar las siguientes:

  1. Observar como el patrón de diseño resuelve los problemas de diseño.
  2. Revisar las secciones de “Objetivo”.
  3. Estudiar la interrelación entre los diferentes patrones. Estudiar estas relaciones puede guiar directamente al patrón correcto o grupo de patrones.
  4. Estudiar los patrones con un propósito similar. Hay patrones que son muy parecidos estudiar sus similitudes y sus diferencias te ayudará en la elección del patrón que mejor se adapte a tu problema.
  5. Examinar las razones de cambio. Mirar las causas de rediseño. Luego mirar los patrones que te ayudan a evitar las causas de rediseño.
  6. Considerar lo que se debería cambiar en el diseño concreto. Este apartado es el opuesto al anterior, es decir al enfocado sobre las razones de cambio. En lugar de considerar que podría forzar un cambio en un diseño, considera lo que quieres que Guía de construcción de software en Java con patrones de diseño sea capaz de cambiar sin rediseñarlo. El objetivo aquí es encapsular el concepto que varía, un fin de muchos patrones de diseño. Son aspectos del diseño que los patrones de diseño permiten que varíen independientemente, por lo tanto puedes hacer cambios sin rediseñar.

Como usar un patrón de diseño

Una vez seleccionado el patrón de diseño que se va a aplicar hay que tener en cuenta las siguientes claves para utilizar el patrón:

  • Leer el patrón de diseño por encima. Prestar una atención particular a las secciones “Aplicabilidad” y “Consecuencias” para asegurarse de que el patrón es correcto para tu problema.
  • Observar la estructura, los elementos que participan en el patrón y las colaboraciones entre ellos. Asegurarse de que comprendes las clases y objetos del patrón y como se relacionan.
  • Mirar la sección “Código del ejemplo” para ver un ejemplo concreto del patrón en código. Estudiar el código te enseñará como implementar el patrón.
  • Escoger nombres significativos de los elementos que participan en el patrón para el contexto de la aplicación. Los nombres de los elementos de un patrón son normalmente demasiado abstractos para aparecer directamente en una aplicación. No obstante, es muy útil incorporar los nombres de los participantes en el nombre que aparece en la aplicación. Esto te ayudará a tener el patrón más explícito en la implementación.
  • Definir las clases. Declarar sus interfaces, establecer sus relaciones de herencia, y definir las variables instanciadas que representan datos y referencias de objetos.
  • Identificar las clases existentes en tu aplicación que el patrón afectará y modificará adecuadamente.
  • Definir nombres específicos para las operaciones en dicha aplicación. Otra vez, los nombres dependen generalmente de la aplicación. Utilizar las responsabilidades y colaboraciones asociadas con cada operación como guía.
  • Implementar las operaciones que conllevan responsabilidad y colaboración en el patrón. La sección “Implementación” te ofrece una guía en la implementación. Los ejemplos de la sección “Código del ejemplo” también te ayudan.

Conclusión

Los patrones de diseño describen la solución a problemas que se repiten una y otra vez en nuestros sistemas, de forma que se puede usar esa solución siempre que haga falta. Capturan el conocimiento que tienen los expertos a la hora de diseñar. Ayudan a generar software “maleable” (software que soporta y facilita el cambio, la reutilización y la mejora). Son guías de diseño, no reglas rigurosas.

En este artículo he realizado un pequeño resumen de los principales patrones de diseño y de algún otro que me ha parecido interesante. En los siguiente artículos explicaré más en profundidad cada uno de estos patrones. Si echáis en falta alguno o hay alguno que no entendáis bien, no dudéis en dejar vuestro comentario.

  • Enrique

    Muy completo! Está muy bien 🙂