Capítulo uno: Introducción
Diseñar software orientado a objetos es complejo, y el software orientado a objetos reutilizable es aún más difícil. Debe encontrar los objetos pertinentes, factorizarlos en clases con la granularidad adecuada, definir interfaces de clase y jerarquías de herencia, y establecer relaciones clave entre ellos. Su diseño debe ser específico para el problema y lo suficientemente general como para abordar problemas y requisitos futuros.
¿Qué es un patrón de diseño?
Un patrón de diseño nombra, abstrae e identifica los aspectos críticos de una estructura de diseño estándar que la hacen útil para crear un diseño orientado a objetos reutilizable. El patrón de diseño identifica las clases e instancias participantes, sus funciones y colaboraciones, y la distribución de responsabilidades. Los patrones de diseño describen problemas de su entorno y explican lo esencial de la solución a ese problema para que pueda utilizar esta solución de forma iterativa. Un patrón de diseño tiene cuatro elementos críticos;
- El nombre de un patrón es un asidero para describir un problema de diseño, sus soluciones y consecuencias en una o dos palabras. Poner nombre a un patrón le permite diseñar a un mayor nivel de abstracción.
- El problema: explica cuándo aplicar el patrón. Explica la situación y su contexto. Describe problemas de diseño específicos como la forma de representar algoritmos como objetos.
- La solución: ilustra los elementos que componen el diseño, sus relaciones, colaboraciones y responsabilidades.
- Las consecuencias: son los resultados y contrapartidas de aplicar el patrón.
Patrones de diseño en Smalltalk MVC
La tríada de clases Modelo/Vista/Controlador (MVC) [KP88] se utiliza para construir interfaces de usuario en Smalltalk-80. El Modelo Vista Controlador tiene tres clases de objetos. El Modelo es el objeto de la aplicación, la Vista es su presentación en pantalla y el Controlador define cómo reacciona la interfaz de usuario a la entrada del usuario. Antes del MVC, los diseños de interfaz de usuario tendían a agrupar estos objetos. Después, el MVC los desacopla para mejorar la flexibilidad y la reutilización. MVC desacopla las vistas y los modelos estableciendo un protocolo de suscripción/notificación. Una vista debe asegurarse de que su apariencia refleja el estado del modelo. Cada vez que los datos del modelo cambian, se lo comunica a las vistas que dependen de él. En respuesta, cada vista tiene la oportunidad de actualizarse. Este enfoque le permite adjuntar varias vistas a un modelo para ofrecer diferentes presentaciones. También puede crear nuevas vistas para un modelo sin reescribirlo.
MVC le permite cambiar la forma en que una vista responde a la entrada del usuario sin cambiar su presentación visual. Es posible que desee cambiar la forma en que responde al teclado, por ejemplo, o utilizar un menú emergente en lugar de teclas de comando. MVC encapsula el mecanismo de respuesta en un objeto controlador. Una jerarquía de clases de controladores facilita la creación de un nuevo controlador como variación de uno ya existente.
Descripción de patrones de diseño
Los patrones de diseño se definen utilizando un formato coherente. Cada patrón se divide en secciones de acuerdo con la siguiente plantilla. La plantilla confiere una estructura uniforme a la información, lo que facilita el aprendizaje, la comparación y el uso de los patrones de diseño.
- Nombre y clasificación del patrón: El nombre transmite sucintamente la esencia del patrón. Una buena reputación es vital porque pasará a formar parte de su vocabulario de diseño. La clasificación del patrón refleja el esquema que introducimos en la sección 1.5.
- Intención: Una breve declaración que responda a las siguientes preguntas: ¿Qué hace el patrón de diseño? ¿Cuál es su razón de ser y su objetivo? ¿Qué cuestión o problema de diseño concreto aborda?
- También conocido como Otros nombres conocidos para el patrón, si los hay.
- Motivación: Un escenario que ilustra un problema de diseño y cómo las estructuras de clases y objetos del patrón resuelven el problema. El sistema le ayudará a comprender la descripción más abstracta del siguiente patrón.
- Aplicabilidad: ¿En qué situaciones puede aplicarse el patrón de diseño? ¿Cuáles son los ejemplos de diseños deficientes que el patrón puede solucionar? ¿Cómo puede reconocer estas situaciones?
- Estructura: Una representación gráfica de las clases del patrón utilizando una notación basada en la Técnica de Modelado de Objetos (OMT) [RBP+91]. También utilizamos diagramas de interacción [JCJO92, Boo94] para ilustrar secuencias de peticiones y colaboraciones entre objetos. El apéndice B describe estas notaciones en detalle.
- Participantes: Las clases y/u objetos que participan en el patrón de diseño y sus responsabilidades.
- Colaboraciones: Cómo colaboran los participantes para llevar a cabo sus responsabilidades.
- Consecuencias: ¿Cómo apoya el patrón sus objetivos? ¿Cuáles son las contrapartidas y los resultados de utilizar el patrón? ¿Qué aspecto de la estructura del sistema le permite variar de forma independiente?
- Implementación: ¿Qué escollos, pistas o técnicas debe conocer a la hora de aplicar el patrón? ¿Existen problemas específicos del lenguaje?
- Código de ejemplo: Fragmentos que ilustran cómo podría implementar el patrón en C++ o Smalltalk.
- Usos conocidos: Ejemplos del patrón encontrados en sistemas reales. Incluimos al menos dos ejemplos de dominios diferentes.
- Patrones relacionados: ¿Qué patrones de diseño están estrechamente relacionados con éste? ¿Cuáles son las diferencias esenciales? ¿Qué otros patrones deberían utilizarse con éste?
Cómo resuelven los patrones de diseño los problemas de diseño
Los patrones de diseño resuelven los problemas de diseño encontrando los objetos apropiados; los patrones de diseño le ayudan a identificar las abstracciones menos obvias y los objetos que pueden capturarlas. Los patrones de diseño también determinan la granularidad de los objetos; los patrones de diseño describen cómo representar subsistemas completos como objetos y soportar un gran número de objetos en las granularidades más finas. Los patrones de diseño le ayudan a definir las interfaces identificando sus elementos esenciales y los tipos de datos que se envían a través de una interfaz. Un patrón de diseño también puede indicarle lo que no debe poner en la interfaz.
Cita favorita del capítulo: «Los patrones de clase Creativos difieren alguna parte de la creación del objeto a las subclases, mientras que los patrones de objeto Creativos la difieren a otro objeto. Los patrones de clase Estructurales utilizan la herencia para componer clases, mientras que los patrones de objetos Estructurales describen formas de ensamblar objetos. Los patrones de clase Comportamiento utilizan la herencia para describir algoritmos y flujos de control, mientras que los patrones de objeto Comportamiento describen cómo coopera un grupo de objetos para realizar una tarea que ningún objeto por sí solo puede llevar a cabo
solo».
Capítulo 2: Un estudio de caso: Diseñar un editor de documentos
Estructura del documento
Un documento dispone elementos gráficos esenciales como caracteres, líneas, polígonos y otras formas. Estos elementos capturan el contenido total de información del documento. Sin embargo, estos elementos no se ven en términos gráficos sino en términos de la estructura física del documento: líneas, columnas, figuras, tablas y otras subestructuras. A su vez, estas subestructuras tienen sus subestructuras, y así sucesivamente. La interfaz de usuario de Lexi debe permitir a los usuarios manipular directamente estas subestructuras. Por ejemplo, un usuario debería poder tratar un diagrama como una unidad en lugar de como una colección de primitivas gráficas individuales. El usuario debería poder referirse a una tabla como un todo, no como una masa informe de texto y gráficos. Eso ayuda a que la interfaz sea sencilla e intuitiva.
Formateo
Tras encontrar una forma de representar la estructura física del documento, debe averiguar cómo construir una estructura física determinada. Una que se corresponda con un documento formateado correctamente. La representación y el formateo son distintos: la capacidad de captar la estructura física del documento no nos dice cómo llegar a un sistema concreto. Esta responsabilidad recae principalmente en Lexi.
Encapsular el algoritmo de formateo: Con todas sus limitaciones y detalles, el proceso de formateo no es fácil de automatizar. Hay muchos enfoques del problema, y la gente ha desarrollado varios algoritmos de formateo con diferentes puntos fuertes y débiles. Dado que Lexi es un editor «lo que ves es lo que obtienes», una compensación necesaria a tener en cuenta es el equilibrio entre la calidad del formateo y la velocidad de formateo. Dado que los algoritmos de formateo tienden a ser complejos, es deseable mantenerlos bien contenidos o -mejor aún- completamente independientes de la estructura del documento. Lo ideal sería poder añadir un nuevo tipo de subclase Glifo sin tener en cuenta el algoritmo de formateo. A la inversa, añadir un nuevo algoritmo de formateo no debería requerir modificar los glifos existentes.
Patrón Estrategia: Encapsular un algoritmo en un objeto es la intención del patrón Estrategia. Los participantes clave en el patrón son los objetos Estrategia, que encapsulan diferentes algoritmos y el contexto en el que operan. Los compositores son estrategias; encapsulan diferentes algoritmos de formato. La composición es el contexto de una estrategia de compositor. La clave para aplicar el patrón Estrategia es diseñar interfaces para el sistema y su contexto que sean lo suficientemente generales como para admitir una serie de algoritmos.
Embellecer la interfaz de usuario
Considere dos adornos en la interfaz de usuario de Lexi. El primero añade un borde alrededor del área de edición de texto para delimitar la página de texto. El segundo añade barras de desplazamiento que permiten al usuario ver distintas partes de la página. Para facilitar la adición y eliminación de estos adornos (especialmente en tiempo de ejecución), no debe utilizar la herencia para añadirlos a la interfaz de usuario. Conseguiremos la máxima flexibilidad si otros objetos de la interfaz de usuario ni siquiera saben que los adornos están ahí. Eso nos permitirá añadir y eliminar los adornos sin cambiar otras clases.
Desde el punto de vista de la programación, embellecer la interfaz de usuario implica ampliar el código existente. Utilizar la herencia para realizar dicha ampliación impide reordenar los embellecimientos en tiempo de ejecución, pero un problema igualmente grave es la explosión de clases resultante de un enfoque basado en la herencia.
Patrón Decorador: El patrón Decorador (196) captura las relaciones entre clases y objetos que admiten el embellecimiento mediante el cerramiento transparente. El término «embellecimiento» tiene un significado más amplio del que hemos considerado aquí. En el patrón Decorador, el embellecimiento se refiere a cualquier cosa que añada responsabilidades a un objeto.
Cita favorita: «La elección de la representación interna del documento afecta a casi todos los aspectos del diseño de Lexi».
Capítulo 3: Patrones de creación
Los patrones de diseño de creación abstraen el proceso de instanciación. Ayudan a que un sistema sea independiente de cómo se crean, componen y representan sus objetos. Un patrón de creación de clases utiliza la herencia para variar la clase instanciada, mientras que un patrón de creación de objetos delegará la instanciación en otro objeto.
Los patrones de creación se vuelven esenciales a medida que los sistemas dependen más de la composición de objetos que de la herencia de clases. A medida que eso sucede, el énfasis se desplaza de la codificación rígida de un conjunto fijo de comportamientos hacia la definición de un grupo más pequeño de comportamientos fundamentales que pueden componerse de otros más complejos. Así, la creación de objetos con comportamientos específicos requiere algo más que la simple instanciación de una clase.
Hay dos temas recurrentes en estos patrones. En primer lugar, todos ellos encapsulan el conocimiento sobre las clases concretas que utiliza el sistema. En segundo lugar, ocultan cómo se crean y juntan las instancias de estas clases. Todo lo que el sistema en general sabe sobre los objetos son sus interfaces definidas por clases abstractas. Los patrones creativos a veces pueden ser competidores. Es decir, hay casos en los que tanto Prototipo como Fábrica Abstracta podrían utilizarse de forma provechosa. Otras veces son complementarios: El constructor puede utilizar uno de los distintos patrones para implementar qué componentes se construyen. El prototipo puede utilizar Singleton en su implementación. Los patrones creativos incluyen;
Fábrica abstracta: Proporcionan una interfaz para crear familias de objetos relacionados o dependientes sin especificar sus clases concretas.
Constructor: Separe la construcción de un objeto complejo de su representación para que el mismo proceso de construcción pueda crear expresiones diferentes.
Método de fábrica: Define una interfaz para crear un objeto pero deja que las subclases decidan qué clase instanciar. El método de fábrica permite a una clase diferir la instanciación a las subclases.
Prototipo: Especifique los tipos de objetos a crear utilizando una instancia prototípica, y cree cosas nuevas copiando este prototipo.
Singleton: Garantizar que una clase sólo tiene una instancia y proporcionar un punto de acceso global a la misma.
Cita favorita del capítulo: «Hay dos temas recurrentes en estos patrones. En primer lugar, todos ellos encapsulan conocimientos sobre las clases concretas que utiliza el sistema. En segundo lugar, ocultan cómo se crean y se juntan las instancias de estas clases».
Capítulo 4: Patrones estructurales
Los patrones estructurales se ocupan de cómo se componen las clases y los objetos para formar estructuras mayores. Los patrones estructurales de clases utilizan la herencia para reunir interfaces o implementaciones. Como ejemplo sencillo, considere cómo
Los estamentos múltiples mezclan dos o más clases en una. El resultado es una clase que combina las propiedades de sus clases padre. Este patrón es útil para hacer que bibliotecas de clases desarrolladas de forma independiente funcionen juntas. Otro ejemplo es la forma de clase del patrón Adaptador. Generalmente, un adaptador hace que una interfaz se ajuste a otra, proporcionando una abstracción uniforme de diferentes interfaces. Un adaptador de clase lo consigue heredando de forma privada de una clase adaptadora. El adaptador expresa entonces su interfaz en términos de los adaptados. Los patrones de objetos estructurales describen formas de componer objetos para realizar nuevas funcionalidades. La flexibilidad añadida de la composición de objetos proviene de la capacidad de cambiar el documento en tiempo de ejecución, lo que es imposible con la composición de clases estáticas. Un patrón de objeto estructural describe la construcción de una jerarquía de clases para dos tipos de objetos: primitivos y compuestos. Los objetos compuestos le permiten componer objetos primitivos y otros objetos compuestos en estructuras arbitrariamente complejas. A continuación se detallan los patrones estructurales que debe conocer;
Adaptador: Convierte la interfaz de una clase en otra interfaz que esperan los clientes. El adaptador permite que funcionen juntas clases que de otro modo no podrían debido a interfaces incompatibles.
Puente: Desacoplar una abstracción de su implementación para que ambas puedan variar de forma independiente.
Componer: Componga objetos en estructuras de árbol para representar jerarquías parte-todo. Composite permite a los clientes tratar de manera uniforme los objetos individuales y las composiciones de objetos.
Decorador: Adjunta responsabilidades adicionales a un objeto de forma dinámica. Los decoradores proporcionan una alternativa flexible a la subclase para ampliar la funcionalidad.
Fachada: Proporcionar una interfaz unificada a un conjunto de interfaces de un subsistema. Una fachada define una interfaz de nivel superior que facilita el uso del subsistema.
Peso mosca: Utilice la compartición para soportar un gran número de objetos de grano fino de forma eficiente.
Proxy: Proporcionar un sustituto o marcador de posición de otro objeto para controlar el acceso al mismo.
Cita favorita: «En lugar de componer interfaces o implementaciones, los patrones de objetos estructurales describen formas de componer objetos para realizar nuevas funcionalidades .»
Capítulo 5: Patrones de comportamiento
Los patrones de comportamiento tienen que ver con los algoritmos y la asignación de responsabilidades entre objetos.
Los patrones de comportamiento no sólo describen patrones de objetos o clases, sino también los patrones de comunicación entre ellos. Estos patrones caracterizan el flujo de control complejo que es difícil de seguir en tiempo de ejecución. Desvían su atención del flujo de control para permitirle concentrarse sólo en cómo están interconectados los objetos. Los patrones de clases de comportamiento utilizan la herencia para distribuir el comportamiento entre las clases. Este capítulo incluye dos de estos patrones. El método de plantilla (360) es el más sencillo y común de los dos. Un método de plantilla es una definición abstracta de un algoritmo. Define el algoritmo paso a paso. Cada paso invoca una operación abstracta o una operación primitiva. Una subclase da cuerpo al algoritmo definiendo las operaciones abstractas. El otro patrón de clase conductual es Intérprete (274), que representa la gramática como una jerarquía de clases e implementa un intérprete como una operación sobre instancias de estas clases. Los patrones de comportamiento de objetos utilizan la composición de objetos en lugar de la herencia. Algunos describen cómo un grupo de objetos pares coopera para realizar una tarea que ningún objeto por sí solo puede llevar a cabo. Una cuestión importante aquí es cómo los objetos pares se conocen entre sí. Los pares podrían mantener referencias explícitas entre sí, lo que aumentaría su acoplamiento. En el extremo, cada objeto sabría de todos los demás. El patrón Mediador (305) evita esto introduciendo un objeto mediador entre los pares. El mediador proporciona la indirección necesaria para un acoplamiento laxo. Estos son algunos de los patrones de comportamiento;
Intérprete: Dado un lenguaje, defina una representación para su gramática y un intérprete que utilice la representación para interpretar las frases del lenguaje.
Petición: Encapsula una solicitud como un objeto, lo que le permite parametrizar clientes con diferentes solicitudes, poner en cola o registrar solicitudes y admitir operaciones que se pueden deshacer.
Iterador: Proporcionan una forma de acceder secuencialmente a los elementos de un objeto agregado sin exponer su representación subyacente.
Mediador: Define un objeto que encapsula cómo interactúa un conjunto de objetos. Mediador promueve el acoplamiento suelto al evitar que los objetos se refieran explícitamente entre sí y permitirle variar su interacción de forma independiente.
Memento: Sin violar la encapsulación, capturar y externalizar el estado interno de un objeto para que el objeto pueda ser restaurado a este estado más tarde.
Observador: Define una dependencia de uno a muchos entre objetos, de modo que cuando un objeto cambia de estado, todos sus dependientes son notificados y actualizados automáticamente.
Estado: Permite que un objeto altere su comportamiento cuando cambia su estado interno. El objeto parecerá cambiar de clase.
Estrategia: Definir una familia de algoritmos, encapsular cada uno de ellos y hacerlos intercambiables. La estrategia permite que el algoritmo varíe independientemente de los clientes que lo utilicen.
Favorito del capítulo: Una cuestión importante aquí es cómo los objetos pares se conocen entre sí».
CÓMO PUEDE AYUDAR ESTE LIBRO A LOS DESARROLLADORES DE SOFTWARE
El libro «Patrones de diseño: Elements of Reusable Object-Oriented Software» de Erich Gamma, Richard Helm, Ralph Johnson y John Vlissides es una obra seminal en ingeniería de software que describe 23 patrones de diseño para la programación orientada a objetos. es un libro que puede ayudar a los desarrolladores de software proporcionándoles soluciones reutilizables a problemas de diseño comunes en el desarrollo de software orientado a objetos. Estos patrones resuelven problemas comunes de diseño y promueven un código flexible, reutilizable y mantenible. Al comprender y aplicar estos patrones, los desarrolladores de software pueden escribir un código más eficiente, modular y extensible, más fácil de comprender, modificar y mantener a lo largo del tiempo.