¿Por qué?
|
|
|
Problemas de Diseño | Problemas de Rediseño | ||
---|---|---|---|
el diseño es difícil y reutilizable es incluso más difícil |
el diseño debería ser específico para el problema actual pero también en general para direccionar futuros problemas y requisitos y, así, evitar rediseños o al menos minimizarlos |
||
Encontrar objetos apropiados |
Cohesión |
Crear un objeto especificando su clase explícitamente |
Acoplamiento |
Relacionar Estructuras del Tiempo de Compilación y de Ejecución |
Acoplamiento |
Dependencias de las representaciones o implementaciones de objetos |
Acoplamiento |
Determinar la granularidad de los objetos |
Tamaño |
Dependencias de plataformas hardware o software |
Acoplamiento |
Especificar interfaces de objetos |
Abstracción |
Dependencias de operaciones concretas |
Acoplamiento |
Especificar la Implementación de Objetos |
Implementación |
Dependencias algorítmicas |
Acoplamiento |
Poner a funcionar los mecanismos de reutilización |
Reusabilidad por composición, herencia y/o parametrización |
Fuerte acoplamiento |
Acoplamiento |
Añadir funcionalidad mediante herencia |
Principio Abierto/Cerrardo |
||
Incapacidad para modificar las clases convenientemente |
Principio Abierto/Cerrardo |
|
|
¿Cuándo? ¿Dónde? ¿Quién?
|
¿Qué?
Cada patron describe un problema el cual ocurre una y otra vez en nuestro entorno y describe el núcleo de una solución para el problema, de tal forma que se puede usar esta solución un millón de veces sin hacerlo de la misma forma dos veces
— Alexander
A Pattern Language |
Nuestras soluciones se expresan en términos de objetos e interfaces en vez de paredes y puertas pero el núcleo de ambas clases de patrones es una solución a un problema en un contexto |
Son la descripción de la comunicación de objetos y clases que son particularizados para resolver un problema de diseño general en un contexto particular
— Gamma et al
Design Patterns |
¿Para qué?
|
|
|
|
|
¿Cómo?
Elementos
Elementos Esenciales
|
Nombre
Elemento | Descripción | Ejemplo |
---|---|---|
Nombre |
|
Singleton |
Sinónimos |
Otros nombres conocidos para el patrón, si los hubiere. |
- |
Problema
Elemento | Descripción | Ejemplo: Singleton |
---|---|---|
Motivación |
Contexto para entender el problema de diseño abstracto (representar algoritmos como objetos, estructura inflexible de clases u objetos, …) y cómo resolver el problema |
|
Intención |
Describe qué razón de ser y qué hace el patrón de diseño sobre el problema que aborda |
Asegurar que una clase tenga una única instancia y proveer un punto de acceso global para ésta |
Aplicabilidad |
Lista de condiciones de ejemplos de diseños pobres que se deben cumplir para que tenga sentido aplicar el patrón |
|
Solución
Elemento | Descripción | Ejemplo: Singleton |
---|---|---|
Estructura |
Diagrama de clases del patrón |
|
Participantes |
Reparto de responsabilidades entre las clases del patrón |
|
Colaboraciones |
Diagrama de secuencia de los objetos de las clases del patrón |
Los clientes acceden a la instancia única a través de la operación Instance de Singleton |
Implementación |
Pistas o técnicas, quizás específicas de un lenguaje estático (C++, Java, …) o dinámico (Smalltalk, Javascript, …) a tener en cuenta al aplicar el patrón |
|
Código de ejemplo |
Ejemplo de código que ilustra una implementación del patrón en diversos lenguajes. |
|
Usos conocidos |
Dos o más ejemplos de diferentes aplicaciones en sistemas reales |
|
Patrones relacionados |
Patrones de diseño estrechamente relacionados por diferencias o posible colaboración |
|
Consecuencias
Elemento | Descripción | Ejemplo: Singleton |
---|---|---|
Consecuencia |
|
|
Clasificación
Patrones Creacionales
|
|
|
|
|
Los clientes no tienen conocimiento (abstracción) sobre las clases concretas que usan mediante sus clases abstractas porque delegan a otras clases (creador) cómo se crean y se asocian las instancias de las clases concretas que usa el sistema. |
|
|
|
|
|
Abstract Factory
Problema
Motivación
Un cirujano opera con el mismo algoritmo, pero dependiendo de la localización, quirófano o selva o …, lo realizará con varios instrumentos pertenecientes a distintas familias equivalentes, cortante como bisturí o navaja o …, secante como compresa o trapo o … |
Esto complica la comprensión de su comportamiento y tiende a crecer a medida que surgen nuevas familias equivalentes. |
|
||
|
|
|
|
|
|
Intención
Si la responsabilidad de cirujano incluye únicamente la solicitud de instrumental abstracto (algo cortante, algo secante, …) y otros, instrumentalistas, se responsabilizan por separado de cómo se obtienen los instrumentos dependiendo de la localización, ninguna clase será compleja y se evitará la tendencia a crecer. |
Cohesión
|
||
Acoplamiento
|
|
|
Granularidad
|
|
|
Intención | Aplicabilidad |
---|---|
La intención de este patrón es proporcionar una interfaz para crear familias de objetos relacionados o dependientes entre sí sin tener que especificar su clase concreta |
|
Solución
Estructura General |
|
Participantes |
|
Implementación
Variaciones
-
Un diseño más flexible, aunque menos seguro, es añadir un parámetro a las operaciones que crean los objetos. Este parámetro especifica el tipo de objeto a ser creado. Podría tratarse de un identificador de clase, un entero, una cadena de texto o cualquier otra cosa que identifique el tipo de producto. De hecho, con este enfoque, AbstractFactory solo necesita una única operación create con un parámetro que indique el tipo de objeto a crear.
-
Podemos aplicarla en Java, C++, etc. sólo cuando todos los objetos tienen la misma clase abstracta o cuando los objetos producto pueden ser convertidos con seguridad al tipo concreto por el objeto que lo solicita. Pero incluso cuando no es necesaria la conversión de tipos, todavía subyace un problema inherente: todos los productos se devuelven al cliente con la misma interfaz abstracta que el tipo de retorno. El cliente no podrá por tanto distinguir o hacer suposiciones seguras acerca de la clase de un producto. En caso de que los clientes necesiten realizar operaciones específicas de las subclases, éstas no estarán accesibles a través de la interfaz abstracta. Aunque el cliente podría hacer una conversión al tipo de una clase hija, eso no siempre resulta viable o seguro, porque la conversión de tipo puede fallar. Éste es el inconveniente típico de una interfaz altamente flexible y extensible.
-
Consecuencias
Ventajas | Desventajas |
---|---|
|
|
Problemas de Diseño y Rediseño
Problemas de Diseño | Problemas de Rediseño | ||
---|---|---|---|
Determinar la granularidad de los objetos |
Granularidad |
Crear un objeto especificando su clase explícitamente |
Acoplamiento |
Especificar la implementación de los objetos |
Implementación |
Dependencias de plataformas hardware o software |
Acoplamiento |
Dependencias de las representaciones o implementaciones de objetos |
Acoplamiento |
||
Fuerte acoplamiento |
Acoplamiento |
Builder
Problema
Motivación
|
|
|
||
|
|
|
|
|
|
Intención
|
|
|
||
|
|
|
|
|
|
Intención | Aplicabilidad |
---|---|
|
|
Solución
Estructura General |
|
Participantes |
|
Implementación
Variaciones
-
En general, los ConcreteProduct creados por los ConcreteBuilder tienen representaciones tan diferentes que suele ser de poca utilidad definir una clase padre común AbstractProduct para los diferentes productos.
Consecuencias
Ventajas | Desventajas |
---|---|
|
Problemas de Diseño y Rediseño
Problemas de Diseño | Problemas de Rediseño | ||
---|---|---|---|
Determinar la granularidad de los objetos |
Granularidad |
Dependencias algorítmicas |
Acoplamiento |
Especificar la implementación de los objetos |
Implementación |
Factory Method
Problema
Motivación
|
|
|
||
|
|
|
|
|
|
Intención
|
|
||
|
|
|
|
|
|
Intención | Aplicabilidad |
---|---|
|
|
Solución
Estructura General |
|
Participantes |
|
Implementación
Variaciones
-
Las dos principales variantes del patrón Factory Method son:
-
La clase Creator es una clase abstracta y no proporciona una implementación para el método de fabricación que declara. Requiere que las subclases definan una implementación porque no hay ningún comportamiento predeterminado razonable.
-
La clase Creator es una clase abstracta (menos común) o concreta y proporciona una implementación predeterminada del método de fabricación. El ConcreteCreator usa el método de fabricación principalmente por flexibilidad. Lo hace siguiendo una regla que dice: “crear objetos en una operación aparte, para que las subclases puedan redefinir el modo en que son creados”. Esta regla asegura que los diseñadores de las subclases puedan cambiar la clase de objetos.
-
-
Otra variante del patrón permite que los métodos de fabricación creen varios tipos de productos. El método de fabricación recibe un parámetro que identifica el tipo de objeto a crear. Todos los objetos creados por el método compartirán la interfaz AbstractProduct.
-
Un potencial problema de los métodos de fabricación es que pueden obligar a usar la herencia sólo para crear los objetos ConcreteProduct apropiados. Otra forma de hacer esto es proporcionar una subclase genérica de Creator parametrizada con la clase de ConcreteProduct.
Consecuencias
Ventajas | Desventajas |
---|---|
|
|
Problemas de Diseño y Rediseño
Problemas de Diseño | Problemas de Rediseño | ||
---|---|---|---|
Especificar la implementación de los objetos |
Implementación |
Crear un objeto especificando su clase explícitamente |
Acoplamiento |
Aplicaciones | ||
---|---|---|
Prototype
Problema
Motivación
|
|
|
||
|
|
|
|
|
|
Intención
|
|
|
||
|
|
|
|
|
|
Intención | Aplicabilidad |
---|---|
|
|
Solución
Estructura General |
|
Participantes |
|
Implementación
Variaciones
|
|
|
||
|
|
|
|
|
|
-
En cuanto al método de clonación, que es la parte más complicada de implementar del patrón Prototype (especialmente cuando las estructuras de objetos contienen referencias circulares), existen dos variantes, copia superficial y copia profunda:
-
Copia superficial: sólo duplica los elementos de alto nivel de una clase; esto proporciona una copia más rápida, pero no siempre resulta apropiada (los objetos de bajo nivel son compartidos entre las copias del objeto, por lo que cualquier cambio en ellos afectaría a todas las copias).
-
Copia profunda: duplica tanto los atributos de alto nivel como los objetos de bajo nivel. Esto suele llevar más tiempo y puede ser costoso para objetos con una estructura compleja, pero asegura que los cambios en una copia se aíslan de las otras copias. Mediante la copia profunda se garantiza que el clon y el original son independientes, es decir, que los componentes del clon son clones de los componentes del prototipo.
-
La mayoría de los lenguajes proporcionan algún tipo de ayuda para clonar objetos (clone(), constructor de copia, …). Pero estas facilidades no resuelven el problema de la copia superficial frente a la copia profunda. La clonación nos obliga a decidir qué será compartido, si es que lo será algo.
-
-
Mientras que a algunos clientes les sirve el clon tal cual, otros necesitarán inicializar parte de su estado interno, o todo, con valores de su elección. Generalmente no se pasan dichos valores en la operación clonar ya que esto impediría tener una interfaz de clonación uniforme, puesto que el número de valores a inicializar variará de unas clases de prototipos a otras (algunos prototipos pueden necesitar múltiples parámetros de inicialización; otros no necesitarán ninguno).
-
Puede darse el caso de que nuestras clases prototipo ya definan operaciones para establecer las partes principales de su estado. Si es así, los clientes pueden usar estas operaciones inmediatamente después de la clonación.
-
Consecuencias
Ventajas | Desventajas |
---|---|
|
Problemas de Diseño y Rediseño
Problemas de Diseño | Problemas de Rediseño | ||
---|---|---|---|
Especificar la implementación de los objetos |
Implementación |
Crear un objeto especificando su clase explícitamente |
Acoplamiento |
Aplicaciones | ||
---|---|---|
Singleton
Problema
Motivación
|
|
|
||
|
|
|
|
|
|
Intención
|
|
|
||
|
|
|
|
|
|
Intención | Aplicabilidad |
---|---|
|
|
Solución
Participantes | Estructura General |
---|---|
|
Implementación
Variaciones
|
Consecuencias
Ventajas | Desventajas |
---|---|
|
Problemas de Diseño y Rediseño
Problemas de Diseño | Problemas de Rediseño | ||
---|---|---|---|
Especificar la implementación de los objetos |
Implementación |
Aplicaciones | ||
---|---|---|
Patrones Estructurales
|
|
|
|
|
|
|
|
|
|
Adapter
Problema
Motivación
|
|
|
||
|
|
|
|
|
|
Intención
|
|
||
|
|
|
Granularidad
|
|
|
Intención | Aplicabilidad |
---|---|
|
|
Solución
Estructura General |
|
|
Participantes |
|
Implementación
Variaciones
-
Adapter de clases: adapta una clase Adaptee a Target, pero se refiere únicamente a una clase Adaptee concreta. Por tanto, un Adapter de clases no nos servirá cuando lo que queremos es adaptar una clase y todas sus subclases.
-
En una implementación en C++ de un Adapter de clases, el Adapter debería heredar públicamente de Target y privadamente de Adaptee. Así, Adapter sería un subtipo de Target , pero no de Adaptee.
-
Se precisa definir las correspondientes operaciones abstractas para la interfaz reducida de Adaptee.
-
-
Adapter de objetos: permite adaptar varias subclases Adaptee existentes, es decir, el Adaptee- en sí y todas sus subclases en caso de que las tenga. En esta situación no resulta práctico adaptar su interfaz heredando de cada una de ellas. Un Adapter de objetos puede adaptar la interfaz de su clase padre.
-
El cliente reenvía las peticiones para acceder a la estructura jerárquica a un objeto delegado.
-
El cliente puede usar una estrategia de adaptación diferente cambiando el objeto delegado.
-
Consecuencias
Ventajas | Desventajas |
---|---|
|
Problemas de Diseño y Rediseño
Problemas de Diseño | Problemas de Rediseño | ||
---|---|---|---|
Incapacidad para modificar las clases convenientemente |
Principio Abierto/Cerrado |
Bridge
Problema
Motivación
|
|
|
||
|
|
|
|
|
|
Intención
|
|
||
|
|
|
|
|
|
Intención | Aplicabilidad |
---|---|
|
|
Solución
Estructura General |
|
Participantes |
|
Implementación
Variaciones
-
En situaciones en las que sólo hay una implementación no es necesario crear una clase abstracta Implementor. Éste es un caso degenerado del patrón Bridge, cuando hay una relación uno-a-uno entre Abstraction e Implementor. Sin embargo, esta separación sigue siendo útil cuando un cambio en la implementación de una clase no debe afectar a sus clientes existentes, es decir, éstos no deberían ser recompilados, sólo enlazados.
-
Para la selección del objeto Implementor existen diversas alternativas:
-
Si Abstraction conoce a todas las clases ConcreteImplementor, puede crear una instancia de una de ellas en su constructor; puede decidir de cuál basándose en los parámetros recibidos en él.
-
Otro enfoque consiste en elegir inicialmente una implementación predeterminada y cambiarla después en función de su uso.
-
También es posible delegar totalmente la decisión en otro objeto. Por ejemplo, Abstract Factory sabe qué tipo de objetos ConcreteImplementor crear para la plataforma en uso. Una ventaja es que Abstraction no está acoplada directamente a ninguna de las clases Implementor.
-
-
Algunas clases de implementación, especialmente las clases sin estado (que no mantienen un estado interno), pueden ser compartidas entre múltiples objetos de la aplicación.
Consecuencias
Ventajas | Desventajas |
---|---|
|
Problemas de Diseño y Rediseño
Problemas de Diseño | Problemas de Rediseño | ||
---|---|---|---|
Poner a funcionar los mecanismos de reutilización (delegación) |
Reusabilidad por composición, herencia y/o parametrización |
Dependencias de plataformas hardware o software |
Acoplamiento |
Dependencias de las representaciones o implementaciones de objetos |
Acoplamiento |
||
Fuerte acoplamiento |
Acoplamiento |
||
Añadir funcionalidad mediante herencia |
Principio Abierto/Cerrado |
Composite
Problema
Motivación
|
|
|
||
|
|
|
|
|
|
Intención
|
|
|
||
|
|
|
|
|
|
Intención | Aplicabilidad |
---|---|
|
|
Solución
Estructura General |
|
Participantes |
|
Implementación
Variaciones
-
La clase Composite siempre es responsable de implementar las operaciones añadir y eliminar para controlar sus hijos, pero existen dos alternativas para definir la interfaz de gestión de los hijos. La decisión implica un equilibrio entre seguridad y transparencia:
-
Definir la gestión de los hijos en la clase Composite nos proporciona seguridad, ya que cualquier intento de añadir o eliminar objetos de las hojas será detectado en tiempo de compilación en un lenguaje estáticamente tipado. Pero perdemos transparencia porque las hojas y los compuestos tienen interfaces diferentes.
-
Definir la interfaz de gestión de los hijos en la raíz de la jerarquía de clases, Component, nos da transparencia, puesto que podemos tratar a todos los componentes de manera uniforme. Sin embargo, sacrifica la seguridad, ya que los clientes pueden intentar hacer cosas sin sentido, como añadir y eliminar objetos de las hojas.
-
Esta implementación a veces entra en conflicto con el principio de diseño de jerarquías de clases que dice que una clase debería definir operaciones que tienen sentido en sus subclases, ya que hay muchas operaciones permitidas por Component que no parecen tener sentido en las clases Leaf.
-
Se debe tener en cuenta también que definir el conjunto de hijos como una variable de instancia de la clase Component, en la que se declaren las operaciones de acceso y gestión de hijos, supone una penalización de espacio para los objetos de la clase Leaf, ya que se está almacenando un puntero que nunca será utilizado por éstos puesto que las hojas nunca tendrán hijos. Por lo tanto, esto sólo merece la pena si hay relativamente pocos hijos en la estructura
-
A veces un poco de creatividad muestra cómo una operación que podría parecer que sólo tiene sentido en el caso de los Composite puede implementarse para todos los Component, moviéndola a la clase Component. Por ejemplo, la interfaz para acceder a los hijos es una parte fundamental de la clase Composite, pero no de la clase Leaf. Pero si vemos a una clase Leaf como un Component para acceder a los hijos que nunca devuelve ningún hijo, las clases Leaf pueden usar esa implementación predeterminada, y las clases Composite pueden redefinir la implementación para devolver sus hijos.
-
-
-
En caso de definir una interfaz para acceder al padre de un componente en la estructura recursiva, se simplifica el recorrido y la gestión de la estructura compuesta, ya que la referencia al padre facilita ascender por la estructura y borrar un componente.
-
El lugar habitual donde definir la referencia al padre es en la clase Component. Las clases Leaf y Composite pueden heredar la referencia y las operaciones que la gestionan.
-
Con referencias al padre, es esencial mantener el invariante de que todos los hijos de un compuesto tienen como padre al compuesto que a su vez los tiene a ellos como hijos. El modo más fácil de garantizar esto es cambiar el padre de un componente sólo cuando se añade o se elimina a éste de un compuesto.
-
-
Otra posible variante del patrón se da cuando se requiere compartir componentes, por ejemplo, para reducir los requisitos de almacenamiento.
-
En caso de que se necesite mantener referencias explícitas al padre de cada componente, los hijos tendrían que almacenar múltiples padres. Pero eso puede llevarnos a ambigüedades cuando se propaga una petición hacia arriba en la estructura.
-
Para simplificar la compartición sería necesario adaptar el diseño para evitar guardar los padres. Esto es factible en casos en los que los hijos puedan evitar enviar peticiones a sus padres externalizando parte de su estado, o todo.
-
Consecuencias
Ventajas | Desventajas |
---|---|
|
|
Problemas de Diseño y Rediseño
Problemas de Diseño | Problemas de Rediseño | ||
---|---|---|---|
Encontrar objetos apropiados |
Cohesión |
Añadir funcionalidad mediante herencia |
Principio Abierto/Cerrado |
Especificar la Implementación de Objetos |
Implementación |
||
Relacionar Estructuras del Tiempo de Compilación y de Ejecución |
Acoplamiento |
Decorator
Problema
Motivación
|
|
|
||
|
|
|
|
|
|
|
|
|
|
Intención
|
- Cohesión * La cohesión de la clase MasterPastryChef es adecuada puesto que asume una única responsabilidad, centrada en colaborar con los oficiales de cocina para investigar e innovar. | ||
---|---|---|
|
|
|
|
|
|
|
|
|
Intención | Aplicabilidad |
---|---|
|
|
Solución
Estructura General |
|
Participantes |
|
Implementación
Variaciones
-
Se puede prescindir de la clase abstracta Decorator cuando sólo se necesita añadir una responsabilidad. Esta situación es habitual cuando se está utilizando una jerarquía de clases existente y no diseñando una nueva. En ese caso, se puede trasladar la responsabilidad de Decorator relativa al reenvío de peticiones al componente a la clase ConcreteDecorator.
Consecuencias
Ventajas | Desventajas |
---|---|
|
|
Problemas de Diseño y Rediseño
Problemas de Diseño | Problemas de Rediseño | ||
---|---|---|---|
Especificar interfaces de objetos |
Especificar interfaces de objetos Abstracción |
Añadir funcionalidad mediante herencia |
Principio Abierto/Cerrado |
Relacionar Estructuras del Tiempo de Compilación y de Ejecución |
Acoplamiento |
Incapacidad para modificar las clases convenientemente |
Principio Abierto/Cerrado |
Facade
Problema
Motivación
|
|
|
||
|
|
|
|
|
|
Intención
|
|
||
|
|
|
|
|
|
Intención | Aplicabilidad |
---|---|
|
|
Solución
Participantes | Estructura General |
---|---|
|
Implementación
Variaciones
-
Puede implementarse el Facade como una interfaz o una clase abstracta, lo cual permite reducir aún más el acoplamiento entre los clientes y el subsistema, ya que los clientes podrán comunicarse con el subsistema a través de dicha interfaz y, definir en un momento posterior, los detalles de la implementación de la fachada concreta que será utilizada para interaccionar con las clases del subsistema.
Consecuencias
Ventajas | Desventajas |
---|---|
|
Problemas de Diseño y Rediseño
Problemas de Diseño | Problemas de Rediseño | ||
---|---|---|---|
Determinar la granularidad de los objetos |
Tamaño |
Fuerte acoplamiento |
Acoplamiento |
Flyweight
Problema
Motivación
|
|
|
||
|
|
|
|
|
|
Intención
|
Cohesión
|
||
|
|
|
|
|
|
Intención | Aplicabilidad |
---|---|
|
|
Solución
Estructura General |
|
Participantes |
|
Implementación
Variaciones
-
No existen variaciones del patrón.
Consecuencias
Ventajas |
|
|
|
||
Desventajas |
|
Problemas de Diseño y Rediseño
Problemas de Diseño | Problemas de Rediseño | ||
---|---|---|---|
Determinar la granularidad de los objetos |
Granularidad |
Proxy
Problema
Motivación
|
|
|
||
|
|
|
|
|
|
Intención
|
|
|
||
|
|
|
|
|
|
Intención | Aplicabilidad |
---|---|
|
|
Solución
Participantes |
Estructura General |
|
Implementación
Variaciones
-
Si una clase Proxy puede tratar con su sujeto sólo a través de una interfaz abstracta, no hay necesidad de hacer una clase Proxy para cada clase RealSubject puesto que el Proxy puede tratar de manera uniforme a todas las clases RealSubject. Esta variación no es posible cuando los objetos Proxy van a crear instancias de RealSubject, ya que en ese caso necesitan conocer la clase concreta.
-
Para implementar algunos tipos de Proxy en los que éste se comporta igual que un puntero, se puede aprovechar en lenguajes como C++ la posibilidad de sobrecarga del operador de acceso a miembros, lo cual permite realizar tareas adicionales cada vez que se desreferencia un objeto. Esto no es posible cuando se necesita saber exactamente qué operación es llamada ya que la sobrecarga el operador de acceso a miembros no permite hacer esta distinción.
Consecuencias
Ventajas | Desventajas |
---|---|
|
|
Problemas de Diseño y Rediseño
Problemas de Diseño | Problemas de Rediseño | ||
---|---|---|---|
Especificar interfaces de objetos |
Abstracción |
Dependencias de las representaciones o implementaciones de objetos |
Acoplamiento |
Patrones de Comportamiento
|
|
|
|
|
|
|
|
|
|
|
|
|
Chain of Responsibility
Problema
Motivación
|
|
|
||
|
|
|
|
|
|
Intención
|
|
||
|
|
|
Granularidad
|
|
|
Intención | Aplicabilidad |
---|---|
|
|
Solución
Participantes |
Estructura General |
|
Implementación
Variaciones
-
Para reenviar la petición a lo largo de la cadena, y para garantizar que los receptores permanecen implícitos, cada objeto de la cadena comparte una interfaz común para procesar peticiones y para acceder a su sucesor en la cadena.
-
Hay dos formas de implementar la cadena sucesora:
-
Utilizar referencias a objetos existentes. Por ejemplo, las referencias al padre en una jerarquía de parte-todo pueden definir el sucesor de una parte. Usar los enlaces existentes es posible cuando éstos permiten la cadena que necesitamos. Nos evita tener que definir explícitamente nuevos enlaces y ahorra espacio.
-
Definir nuevos enlaces. Si no hay referencias preexistentes para definir una cadena, o los que existen no reflejan la cadena de responsabilidad que necesita nuestra aplicación, habrá que definir enlaces nuevos. En este caso, Handler no sólo define la interfaz para las peticiones, sino que normalmente también se encarga de mantener el sucesor. Eso permite que Handler proporcione una implementación predeterminada de handleRequest que reenvíe la petición al sucesor (si hay alguno). Si una subclase de ConcreteHandler no está interesada en dicha petición, no tiene que redefinir la operación de reenvío, puesto que la implementación predeterminada la reenvía incondicionalmente.
-
-
Hay varias opciones para representar las peticiones:
-
En su forma más simple, una petición es una invocación a una operación insertada en el código. Esto resulta conveniente y seguro, pero entonces sólo se pueden reenviar el conjunto prefijado de peticiones que define la clase Handler.
-
Una alternativa más flexible consiste en usar una única función handleRequest que reciba un código de petición como parámetro, lo cual permite un número arbitrario de peticiones. Pero requiere que el emisor y el receptor se pongan de acuerdo sobre cómo debe codificarse la petición y se necesitan sentencias condicionales para despachar la petición en función de su código. Uno de los principales inconvenientes de este enfoque es que no hay un modo seguro de pasar los parámetros con respecto al tipo, por lo que éstos deben ser empaquetados y desempaquetados manualmente.
-
-
Una alternativa para resolver el problema del paso de parámetros consiste en utilizar objetos aparte para las peticiones que empaqueten los parámetros de la petición. Una clase Petición puede representar peticiones explícitamente, y se pueden definir nuevos tipos de peticiones mediante herencia. Las subclases pueden definir diferentes parámetros. Los manejadores deben conocer el tipo de petición para acceder a estos parámetros.
Consecuencias
Ventajas | Desventajas |
---|---|
|
|
Problemas de Diseño y Rediseño
Problemas de Diseño | Problemas de Rediseño | ||
---|---|---|---|
Especificar la Implementación de Objetos |
Implementación |
Dependencias de operaciones concretas |
Acoplamiento |
Relacionar Estructuras del Tiempo de Compilación y de Ejecución |
Acoplamiento |
Fuerte acoplamiento |
Acoplamiento |
Poner a funcionar los mecanismos de reutilización |
Reusabilidad por composición, herencia y/o parametrización |
Añadir funcionalidad mediante herencia |
Principio Abierto/Cerrado |
Command
Problema
Motivación
|
|
|
||
|
|
|
|
|
|
Intención
|
|
||
|
|
|
|
|
|
Intención | Aplicabilidad |
---|---|
|
|
Solución
Participantes |
Estructura General |
|
Implementación
Variaciones
-
Un comando puede tener un rango diverso de habilidades:
-
Se puede limitar a definir un enlace entre un receptor y las acciones que lleva a cabo la petición.
-
Se puede ocupar de implementar todas las acciones ella misma sin delegar para nada en el receptor. Este último extremo resulta útil cuando queremos definir comandos que sean independientes de las clases existentes, cuando no existe ningún receptor adecuado o cuando un comando conoce implícitamente a su receptor.
-
En algún punto entre estos dos extremos se encuentran los comandos que tienen el conocimiento suficiente para encontrar dinámicamente sus receptores.
-
-
Los comandos pueden permitir capacidades de deshacer y repetir si proveen un modo de revertir su ejecución. Una clase ConcreteCommand podría necesitar almacenar información de estado adicional para hacer esto. Este estado puede incluir:
-
El objeto Receiver, el cual es quien realmente realiza las operaciones en respuesta a la petición;
-
Los argumentos de la operación llevada a cabo por Receiver; y
-
Cualquier valor original de Receiver que pueda cambiar como resultado de manejar la petición. El receptor debe proporcionar operaciones que permitan al comando devolver el receptor a su estado anterior.
-
La histéresis (tendencia de un material a conservar una de sus propiedades, en ausencia del estímulo que la ha generado) puede ser un problema a la hora de garantizar un mecanismo de deshacer/rehacer fiable, que preserve la semántica. Los errores se pueden acumular a medida que se ejecutan y deshacen los comandos repetidamente, de manera que el estado de una aplicación finalmente difiera de sus valores originales. Por tanto, puede ser necesario guardar más información con el comando para asegurar que los objetos son devueltos a su estado original
-
-
Se pueden permitir diferentes niveles de deshacer/rehacer. Para permitir un nivel, sólo es necesario guardar el último comando que se ejecutó. Para múltiples niveles es necesario guardar un historial de los comandos que han sido ejecutados, donde la máxima longitud de la lista determina el número de niveles permitidos. Recorrer la lista hacia atrás deshaciendo los comandos cancela sus efectos; recorrerla hacia delante ejecutando los comandos los rehace.
-
Un comando anulable puede que necesite ser copiada antes de ser guardada en el historial. Puesto que el objeto Command que llevó a cabo la petición original más tarde ejecutará otras peticiones, la copia es necesaria para distinguir entre diferentes invocaciones del mismo comando si su estado puede variar entre invocaciones sucesivas. En caso de que el estado del comando no cambie tras su ejecución no es necesario realizar la copia, basta con guardar en el historial una referencia al comando.
-
Se pueden ensamblar comandos en un comando compuesto (MacroCommand).
-
Este tipo de comandos no tienen ningún receptor explícito, sino que son los comandos que contiene las que definen su propio receptor.
-
Si el MacroCommand soporta la función deshacer, todos los comandos internos también deben soportarla. Cuando se llama a la función para invertir el comando, esta llamada debe ser redirigida a los hijos en el orden inverso de la operación execute.
-
Consecuencias
Ventajas | Desventajas |
---|---|
|
Problemas de Diseño y Rediseño
Problemas de Diseño | Problemas de Rediseño | ||
---|---|---|---|
Determinar la granularidad de los objetos |
Tamaño |
Dependencias de operaciones concretas |
Acoplamiento |
Especificar la Implementación de Objetos |
Implementación |
Fuerte acoplamiento |
Acoplamiento |
Interpreter
Problema
Motivación
|
|
|
||
|
|
|
|
|
|
Intención
|
|
||
|
|
|
|
|
|
Intención | Aplicabilidad |
---|---|
|
|
Solución
Estructura General |
|
Participantes |
|
Implementación
Variaciones
-
El patrón Interpreter no especifica cómo crear un árbol sintáctico abstracto ni se ocupa de llevar a cabo el análisis sintáctico. El árbol sintáctico abstracto puede ser creado mediante un analizador sintáctico dirigido por una tabla, mediante un analizador sintáctico (normalmente recursivo descendente) hecho a mano o directamente por el Director.
-
En caso de que se vaya a crear un intérprete que realice múltiples operaciones sobre los árboles sintácticos abstractos, para evitar definir estas operaciones en cada clase de la gramática, será preferible utilizar el patrón Visitor.
-
En aquellas gramáticas cuyas sentencias tengan muchas repeticiones de uno o varios símbolos terminales, puede ser beneficioso compartir una única copia de dichos símbolos. Las gramáticas de los programas software son buenos ejemplos de ello.
-
Los nodos terminales generalmente no guardan información sobre su posición en el árbol sintáctico abstracto.
-
Los nodos padre les pasan cualquier información de contexto que necesiten para llevar a cabo la interpretación, incluida su posición en el árbol sintáctico abstracto.
-
Por tanto, se da una distinción entre estado compartido (intrínseco) y estado recibido (extrínseco), que permite utilizar el patrón FlyWeight.
-
Consecuencias
Ventajas | Desventajas |
---|---|
|
|
Problemas de Diseño y Rediseño
Problemas de Diseño | Problemas de Rediseño |
---|
Iterator
Problema
Motivación
|
|
||
|
|
|
|
|
|
Intención
|
|
||
|
|
|
|
|
|
Intención | Aplicabilidad |
---|---|
|
|
Solución
Estructura General |
|
Participantes |
|
Implementación
Variaciones
-
Un ConcreteIterator puede ser interno o externo dependiendo de quién controla la iteración:
-
Los iteradores externos proporcionan métodos para que los clientes naveguen por el objeto agregado. Es decir, los clientes que utilizan un iterador externo deben avanzar en el recorrido demandando explícitamente el siguiente elemento del agregado al iterador.
-
Los iteradores internos recorren los elementos llevando a cabo alguna acción solicitada por el cliente. Es decir, el cliente entrega al iterador interno una operación a realizar, y el iterador aplica la operación a cada elemento del agregado.
-
-
El algoritmo de recorrido puede ser definido en el ConcreteIterator o en el ConcreteAggregate:
-
Si el algoritmo de recorrido necesita acceder a las variables privadas del agregado, para evitar que el iterador viole la encapsulación de éste, el propio agregado puede definir el algoritmo y utilizar el iterador para almacenar sólo el estado del recorrido. Este tipo de iteradores se conocen como cursor, ya que se limitan a señalar la posición actual en el agregado. Los clientes invocarán sobre el agregado la operación next con el cursor como un argumento y la operación next cambiará el estado del cursor.
-
Si el algoritmo de recorrido se define en el iterador, es más fácil usar diferentes algoritmos de iteración sobre el mismo agregado o reutilizar el mismo algoritmo sobre diferentes agregados.
-
-
Puede ser peligroso modificar un agregado mientras un iterador lo está recorriendo. Dependiendo de la manera de gestionar esta circunstancia, un ConcreteIterator puede ser estático o dinámico:
-
Los iteradores estáticos crean una imagen del agregado en el momento de ser creados y hacen referencia a esa copia mientras son utilizados.
-
Es una solución sencilla pero demasiado costosa como para ser empleada siempre.
-
-
Los iteradores dinámicos hacen referencia directamente al agregado subyacente, por lo que deben garantizar que siempre reflejan su estado.
-
Un iterador robusto asegura que las inserciones y borrados no interfieren en el recorrido.
-
Hay muchas maneras de implementar iteradores robustos. La mayoría lo hacen registrando los iteradores con el agregado. En la inserción o borrado, el agregado o bien ajusta el estado interno de los iteradores que ha producido, o se mantiene información internamente para asegurar el recorrido adecuado.
-
-
-
Los iteradores nulos son iteradores degenerados que facilitan el recorrido de agregados con estructura de árbol, ya que permiten implementar el recorrido sobre una estructura compuesta de un modo uniforme. En cada punto del recorrido se puede pedir el elemento actual a un iterador para sus hijos. Los elementos Composite devuelven, como norma general, un iterador concreto. Pero los elementos Leaf devuelven una instancia de iterador nulo, que se caracterizan porque su operación isDone siempre se evalúa a verdadero. De esta manera es posible utilizar la recursividad para visitar todos los nodos del árbol.
Consecuencias
Ventajas | Desventajas |
---|---|
|
|
Problemas de Diseño y Rediseño
Problemas de Diseño | Problemas de Rediseño | ||
---|---|---|---|
Dependencias algorítmicas |
Acoplamiento |
Mediator
Problema
Motivación
|
|
|
||
|
|
|
|
|
|
Intención
|
|
||
|
|
|
|
|
|
Intención | Aplicabilidad |
---|---|
|
|
Solución
Participantes |
Estructura General |
|
Implementación
Variaciones
-
La clase abstracta Mediator se puede omitir si los colegas siempre trabajan sólo con un mediador.
-
Existen diversos enfoques para la comunicación Colleague-Mediator:
-
Se puede implementar el mediador como un observador, utilizando el patrón Observer. Las clases colegas harán las veces de sujetos, enviando notificaciones al mediador cada vez que cambie su estado; y el mediador responderá propagando los efectos del cambio a otros colegas.
-
Se puede definir en el mediador una interfaz de notificación especializada que permite a los colegas ser más directos en su comunicación, de tal manera que cuando un colega se comunica con el mediador, se pasa a sí mismo como argumento permitiendo así al mediador identificar al emisor.
-
Consecuencias
Ventajas | Desventajas |
---|---|
|
|
Problemas de Diseño y Rediseño
Problemas de Diseño | Problemas de Rediseño | ||
---|---|---|---|
Poner a funcionar los mecanismos de reutilización |
Reusabilidad por composición, herencia y/o parametrización |
Fuerte acoplamiento |
Acoplamiento |
Memento
Problema
Motivación
|
|
|
||
|
|
|
|
|
|
Intención
|
|
||
|
|
|
|
|
|
Intención | Aplicabilidad |
---|---|
|
|
Solución
Participantes |
Estructura General |
|
Implementación
Variaciones
-
La implementación del patrón Memento es muy dependiente del lenguaje de implementación:
-
Lo ideal sería que el lenguaje de implementación permitiese dos niveles de protección estática. C++ lo permite haciendo que Originator sea una clase amiga de Memento, y manteniendo privada la interfaz amplia del Memento y pública la interfaz reducida.
-
En otros lenguajes como Java, debido a que el Memento debería ser accesible únicamente desde el Originator, se puede hacer que el Memento sea una clase interna pública del Originator, declarando todos los métodos privados para que sólo estén disponibles para el objeto Memento y su clase contenedora Originator, ofreciendo así la encapsulación apropiada. Dado que el objeto Memento debe ser independiente de una instancia específica de un Originator, la clase Memento será una clase interna estática.
-
-
La cantidad de información que almacena el objeto Memento variará en función de lo que necesite el objeto Originator para ser capaz de volver a un estado previo. En caso de que los Memento se creen y se devuelvan en una secuencia predecible, es decir, en un orden concreto, el Memento puede guardar únicamente el cambio con respecto al estado interno anterior del Originator.
Consecuencias
Ventajas | Desventajas |
---|---|
|
|
Problemas de Diseño y Rediseño
Problemas de Diseño | Problemas de Rediseño | ||
---|---|---|---|
Especificar interfaces de objetos |
Abstracción |
Dependencias de las representaciones o implementaciones de objetos |
Acoplamiento |
Observer
Problema
Motivación
|
|
|
||
|
|
|
|
|
|
Intención
Una persona puede tener distintos tipos de problemas en la vida, pérdida de un ser querido o padecimiento de una enfermedad o falta de liquidez o …, ante los que notifica que requiere ayuda a una serie de personas de su entorno que le atienden, madre o pareja o jefe o…, para sobrellevar este valle de lágrimas. |
|
||
|
|
|
|
|
|
Intención | Aplicabilidad |
---|---|
|
|
Solución
Estructura General |
|
Participantes |
|
Implementación
Variaciones
-
Hay diversas opciones para implementar la correspondencia entre los sujetos observados y sus observadores:
-
La forma más simple para que un sujeto realice el seguimiento de los observadores a los que debería notificar sería almacenar explícitamente las referencias a los observadores. Sin embargo, esto puede ser muy costoso desde el punto de vista del espacio de almacenamiento cuando hay muchos sujetos observados y pocos observadores.
-
Otra alternativa es utilizar un array asociativo para búsquedas (por ejemplo, una tabla hash) que mantiene la correspondencia entre sujetos observados y sus observadores. Este enfoque consigue reducir el coste de almacenamiento a costa de incrementar el tiempo de acceso, consiguiéndose así un compromiso espacio/tiempo.
-
-
En caso de que un observador dependa de más de un sujeto observado, es necesario extender el interfaz de actualización para permitir al observador conocer qué sujeto observado está enviando la notificación. El sujeto observado puede simplemente enviarse a sí mismo como parámetro de la operación de actualización permitiendo al observador conocer a qué sujeto consultar.
-
Existen dos alternativas para disparar las actualizaciones que mantienen consistentes los estados del sujeto y sus observadores utilizando el mecanismo de notificación:
-
Las operaciones que modifican el estado del sujeto se responsabilizan de enviar la notificación a los observadores después de cambiar el estado del sujeto.
-
La ventaja de este enfoque es que los clientes no tienen que estar pendientes de indicar al sujeto que envíe las notificaciones.
-
La desventaja es que varios cambios consecutivos en el sujeto causarán notificaciones consecutivas a los observadores, que dispararán sus correspondientes actualizaciones, lo cual puede resultar ineficiente.
-
-
Los clientes se responsabilizan de enviar las notificaciones en el momento adecuado.
-
La ventaja es que el cliente puede esperar para hacer el envío de éstas hasta que se hayan realizado una serie de cambios de estado en el sujeto observado, evitando de este modo actualizaciones intermedias innecesarias por parte de los observadores.
-
La desventaja es que los clientes tienen una responsabilidad añadida para activar la actualización, lo cual incrementa la probabilidad de errores, ya que los clientes podrían olvidarse, en un momento dado, de hacer el envío.
-
-
-
En caso de que un sujeto observado sea eliminado, no deben producirse referencias “colgadas” de sus observadores. Una forma de evitar esto es que el sujeto observado notifique a sus observadores cuando se elimina, de modo que éstos puedan reinicializar la referencia a él. En general, la alternativa de borrar los observadores no es una opción, ya que otros objetos pueden hacer referencia a ellos, o ellos mismos pueden estar observando a otros sujetos.
-
Es importante asegurar que el estado del sujeto observado es consistente consigo mismo antes de enviar las notificaciones a los observadores, ya que éstos consultarán al sujeto por su estado actual durante el proceso de actualizar su propio estado.
-
Esta regla de auto-consistencia se puede violar involuntariamente cuando subclases del sujeto observado llaman a métodos heredados. Para evitarlo, se pueden utilizar métodos plantilla cuya última operación sea el envío de notificaciones, y en la que las subclases únicamente redefinirán las operaciones previas al envío según sus responsabilidades.
-
-
Habitualmente, el sujeto observado transmite información adicional acerca del cambio experimentado como un argumento de la actualización. La cantidad de información puede variar ampliamente. Existen diversos enfoques para esta transmisión entre Subject-Observer:
-
En un extremo, se encuentra el modelo de inserción (push), en el cual el sujeto observado envía información detallada a los observadores sobre el cambio, lo quieran o no. El modelo de inserción asume que los sujetos saben algo acerca de las necesidades de sus observadores.
-
En el otro extremo, se encuentra el modelo de extracción (pull), en el cual el sujeto observado envía únicamente la notificación mínima, y los observadores consultarán por los detalles de forma explícita posteriormente. El modelo de extracción hace hincapié en la ignorancia del sujeto sobre sus observadores.
-
-
La complejidad de la relación de dependencia entre los sujetos y los observadores puede requerir la utilización de un objeto que encapsule la semántica de las actualizaciones, denominado ChangeManager. Su objetivo es reducir al mínimo el trabajo necesario para hacer que los observadores reflejen un cambio en sus observados. Por ejemplo, si una operación involucra cambios en varios sujetos observados interdependientes, puede que tenga que asegurarse de que sus observadores son notificados después de que todos los sujetos se han modificado para evitar la notificación a los observadores más de una vez. ChangeManager tiene tres responsabilidades:
-
Relacionar un sujeto observado y sus observadores, proporcionando una interfaz para mantener esta relación. Se elimina así la necesidad de que los sujetos observados mantengan las referencias a sus observadores y viceversa.
-
Definir una estrategia de actualización en particular.
-
Actualizar todos los observadores dependientes a petición de un sujeto.
-
-
Se puede mejorar la eficiencia en la actualización mediante la ampliación de interfaz de registro del sujeto observado permitiendo que los observadores se registren sólo para eventos específicos de su interés en lugar de hacer un registro genérico que incluiría cualquier evento. Cuando tal evento se produce, el sujeto observado informa sólo a aquellos observadores que hayan registrado interés en ese evento. Una forma de apoyar esto es utilizar la noción de aspectos en los objetos sujeto.
Consecuencias
Ventajas | Desventajas |
---|---|
|
|
Problemas de Diseño y Rediseño
Problemas de Diseño | Problemas de Rediseño | ||
---|---|---|---|
Especificar la Implementación de Objetos |
Implementación |
Fuerte acoplamiento |
Acoplamiento |
Relacionar Estructuras del Tiempo de Compilación y de Ejecución |
Acoplamiento |
Añadir funcionalidad mediante herencia |
Principio Abierto/Cerrado |
State
Problema
Motivación
|
|
|
||
|
|
|
|
|
|
Intención
|
|
|
||
|
|
|
|
|
|
Intención | Aplicabilidad |
---|---|
|
|
Solución
Participantes | Estructura General |
---|---|
|
Implementación
Variaciones
-
El patrón State no especifica qué participante define los criterios para las transiciones entre estados. Se puede optar entre diversas opciones:
-
Si estos criterios son fijos, pueden implementarse enteramente en Context.
-
No obstante, es generalmente más flexible y conveniente que sean las propias subclases ConcreteState quienes especifiquen su estado sucesor y cuándo llevar a cabo la transición. Esto requiere añadir una interfaz a Context que permita a los objetos State asignar explícitamente el estado actual del contexto.
-
Descentralizar de esta forma la lógica de la transición facilita modificar o extender dicha lógica definiendo nuevas subclases de State.
-
Una desventaja de la descentralización es que una subclase de State conocerá al menos a otra, lo que introduce dependencias de implementación entre subclases.
-
-
También existe otra alternativa basada en tablas de correspondencia. Para cada estado, una tabla hace corresponder cada posible entrada con un estado sucesor. Este enfoque convierte código condicional (y funciones virtuales, en el caso del patrón State) en una tabla de búsqueda. La principal ventaja de las tablas es su regularidad: se pueden cambiar los criterios de transición modificando datos en vez del código del programa. No obstante, este enfoque presenta algunos inconvenientes:
-
Es menos eficiente que una llamada a una función.
-
Los criterios de transición son menos explícitos.
-
Es más difícil realizar algún tipo de procesamiento arbitrario con cada transición. Para ello es necesario complementar esta técnica con algún otro mecanismo.
-
-
-
Existen dos alternativas para la creación de los objetos State:
-
Instanciación diferida, es decir, creación de los objetos cuando se necesitan (y destrucción cuando dejan de ser necesarios).
-
Este enfoque es preferible cuando no se conocen los estados en tiempo de ejecución y los contextos cambian de estado con poca frecuencia. Se evita así crear objetos que no se usarán nunca, lo que puede ser importante si los objetos State guardan una gran cantidad de información.
-
-
Creación inicial (sin destrucción).
-
Este enfoque es más adecuado cuando los cambios tienen lugar rápidamente, en cuyo caso interesa evitar destruir los objetos State, ya que pueden volver a necesitarse de nuevo en breve. Los costes de creación se pagan al principio y no existen costes de destrucción. No obstante, esta alternativa tiene el inconveniente de que el objeto Context debe guardar referencias a todos los estados en los que pueda entrar.
-
-
Consecuencias
Ventajas | Desventajas |
---|---|
|
|
Problemas de Diseño y Rediseño
Problemas de Diseño | Problemas de Rediseño | ||
---|---|---|---|
Encontrar objetos apropiados |
Cohesión |
||
Especificar la Implementación de Objetos |
Implementación |
||
Poner a funcionar los mecanismos de reutilización |
Reusabilidad por composición, herencia y/o parametrización |
Strategy
Problema
Motivación
|
|
|
||
|
|
|
|
|
|
Intención
|
|
|
||
|
|
|
|
|
|
Intención | Aplicabilidad |
---|---|
|
|
Solución
Participantes | Estructura General |
---|---|
|
Implementación
Variaciones
-
Las interfaces Strategy y Context deben permitir a una ConcreteStrategy acceder de manera eficiente a cualquier dato que ésta necesite del contexto, y viceversa. Existen diversas opciones:
-
Un enfoque es que Context pase los datos como parámetros a las operaciones de Strategy, es decir, llevar los datos a la estrategia. Esto mantiene a Strategy y Context desacoplados. El principal inconveniente de esta alternativa es que Context podría pasar datos a la Strategy que ésta no necesita.
-
Otra técnica es que Context se pase a sí mismo como argumento, y que Strategy le pida los datos explícitamente al contexto. Como alternativa, la estrategia puede guardar una referencia a su contexto, eliminando así la necesidad de pasar nada. El principal inconveniente de este enfoque es que Context debe definir una interfaz más elaborada para sus datos, lo que acopla más estrechamente a Strategy y Context.
-
-
Se pueden utilizar las plantillas para configurar una clase con una estrategia. Esta técnica solo es aplicable si se puede seleccionar la estrategia en tiempo de compilación y no hay que cambiarla en tiempo de ejecución. En este caso, la clase Context se define en una clase plantilla que tiene como parámetro una clase Strategy.
-
Se puede proporcionar en Context un comportamiento predeterminado y hacer opcionales los objetos Strategy. Context comprueba si tiene un objeto Strategy antes de acceder a él y, en caso de que exista, lo usa normalmente. Pero, en caso de que no tenga sentido tener un objeto Strategy y éste no exista, Context realiza el comportamiento predeterminado. La ventaja de este enfoque es que los clientes solo tienen que tratar con los objetos Strategy cuando no les sirva el comportamiento por defecto.
Consecuencias
Ventajas | Desventajas |
---|---|
|
|
Problemas de Diseño y Rediseño
Problemas de Diseño | Problemas de Rediseño | ||
---|---|---|---|
Encontrar objetos apropiados |
Cohesión |
Dependencias algorítmicas |
Acoplamiento |
Especificar la Implementación de Objetos |
Implementación |
Añadir funcionalidad mediante herencia |
Principio Abierto/Cerrado |
Poner a funcionar los mecanismos de reutilización |
Reusabilidad por composición, herencia y/o parametrización |
Template Method
Problema
Motivación
|
|
|
||
|
|
|
|
|
|
Intención
|
|
|
||
|
|
|
|
|
|
Intención | Aplicabilidad |
---|---|
|
|
Solución
Estructura General |
|
Participantes |
|
Implementación
Variaciones
-
Las dos principales variantes del patrón Template Method son:
-
La clase AbstractClass es una clase abstracta y no proporciona una implementación para las operaciones primitivas que declara. Requiere que las subclases definan una implementación porque no hay ningún comportamiento predeterminado razonable.
-
La clase AbstractClass es una clase abstracta (menos común) o concreta y proporciona una implementación predeterminada para las operaciones primitivas que declara. Estas operaciones se denominan operaciones de enganche, y permiten controlar la extensión de subclases en puntos específicos. Una operación de enganche normalmente no hace nada por omisión, por lo que es fundamental identificarlas convenientemente para que no sean obviadas.
-
Consecuencias
Ventajas | Desventajas |
---|---|
|
|
Problemas de Diseño y Rediseño
Problemas de Diseño | Problemas de Rediseño | ||
---|---|---|---|
Poner a funcionar los mecanismos de reutilización |
Reusabilidad por composición, herencia y/o parametrización |
Dependencias algorítmicas |
Acoplamiento |
Visitor
Problema
Motivación
|
|
|
||
|
|
|
|
|
|
Intención
|
|
|
||
|
|
|
|
|
|
Intención | Aplicabilidad |
---|---|
|
|
Solución
Estructura General |
|
Participantes |
|
Implementación
Variaciones
-
El patrón Visitor permite añadir operaciones a clases sin modificar éstas. Esto se logra mediante la técnica de doble-despacho, llamada así porque la operación que se ejecuta depende del tipo de solicitud y de los tipos de dos receptores.
-
Accept es una operación de doble-despacho, puesto que su significado depende de dos tipos: el del Visitor y el del Element. El doble-despacho permite a los ConcreteVisitor solicitar diferentes operaciones en cada clase ConcreteElement.
-
Esta es la clave del patrón Visitor: la operación que se ejecuta depende tanto del tipo de Visitor como del tipo de Element visitado. En vez de enlazar operaciones estáticamente en la interfaz de Element, se pueden fusionar las operaciones en un Visitor y usar accept para hacer el enlace en tiempo de ejecución. Extender la interfaz de Element consiste en definir una nueva subclase de Visitor en vez de muchas nuevas subclases de Element.
-
-
El algoritmo de recorrido que un visitante debe utilizar para visitar cada elemento de la estructura de objetos puede ser definido en la estructura de objetos, en un objeto iterador aparte o en el visitante:
-
En muchas ocasiones es la propia estructura de objetos la responsable de la iteración. Una colección simplemente iterará sobre sus elementos, llamando a la operación accept de cada elemento. Un objeto Composite generalmente se recorrerá a sí mismo haciendo que cada operación accept recorra los hijos del elemento y llame a accept sobre cada uno de ellos recursivamente.
-
Otra solución es utilizar un iterador para visitar los elementos, bien sea externo o interno, dependiendo de la disponibilidad y la eficiencia.
-
Puesto que los iteradores internos son implementados por la estructura de objetos, usar un iterador interno es muy similar a que sea la estructura de objetos la responsable de la iteración. La principal diferencia estriba en que un iterador interno no provocará un doble-despacho, sino que llamará a una operación del visitante con un elemento como argumento, frente a llamar a una operación del elemento con el visitante como argumento. Pero resulta sencillo usar el patrón Visitor con un iterador interno si la operación del visitante simplemente llama a la operación del elemento sin recursividad.
-
-
Otra alternativa consiste en ubicar el algoritmo de recorrido en el visitante, si bien en ese caso se duplica el código del recorrido en cada ConcreteVisitor para cada agregado ConcreteElement. La principal razón para seguir este enfoque es poder implementar un recorrido especialmente complejo que dependa de los resultados de las operaciones realizadas sobre la estructura de objetos.
-
Consecuencias
Ventajas | Desventajas |
---|---|
|
|
Problemas de Diseño y Rediseño
Problemas de Diseño | Problemas de Rediseño | ||
---|---|---|---|
Determinar la granularidad de los objetos |
Tamaño |
Dependencias algorítmicas |
Acoplamiento |
Especificar interfaces de objetos |
Abstracción |
Incapacidad para modificar las clases convenientemente |
Principio Abierto/Cerrado |
Poner a funcionar los mecanismos de reutilización |
Reusabilidad por composición, herencia y/o parametrización |
Principios de Diseño
Diseño Modular
Tipo | Patrón de Diseño | Modularizacón | Cadenas | Analogía |
---|---|---|---|---|
Estructural |
Facade |
por subsistemas cohesivos |
arbol N |
contratista en obra |
Estructural |
Flyweight |
por inmutabilidad |
secuencia 2 |
carta y carta en partida |
Estructural |
Adapter |
por recubrimiento |
secuencia 2 |
adaptador de enchufes |
Comportamiento |
Memento |
por estado |
secuencia 3 |
versiones de plan |
Diseño Orientado a Objetos
Tipo | Patrón de Diseño | Modularizacón por criterio de Extensión | Cadenas | Analogía |
---|---|---|---|---|
Comportamiento |
Template Method |
por especialización |
no ha lugar |
profesor en contextos |
Creacional |
Factory Method |
por creador especializado |
no ha lugar |
repartidor en vehículo |
Estructural |
Composite |
por cardinalidad |
arbol N |
juego con jugador y equipo |
Estructural |
Proxy |
por accesibilidad |
secuencia 2 |
jefe y sustituto |
Comportamiento |
Iterator |
por recorrido* |
secuencia 2 |
dependiente de almacén |
Comportamiento |
State |
por estado |
secuencia 2 |
persona con estados de ánimo |
Comportamiento |
Strategy |
por estrategia* |
secuencia 2 |
persona con estrategias de ligar |
Estructural |
Bridge |
por criterio |
secuencia 2 |
asesores y analistas |
Comportamiento |
Visitor |
por método* |
secuencia 2 |
paciente en hospital |
Comportamiento |
Observer |
por evento* |
secuencia 3 |
persona y apoyos |
Estructural |
Decorator |
por subtarea* |
secuencia N |
novato y oficial de cocina |
Comportamiento |
Chain of Responsibility |
por subtarea* |
secuencia N |
resolutores de reclamación |
Comportamiento |
Command |
por subtarea* |
secuencia 3 |
camareros y comandas |
Comportamiento |
Interpreter |
por subtarea* |
arbol N |
operador y máquina |
Creacional |
Abstract Factory |
por creador familiar* |
secuencia 3 |
cirujano e instrumentalistas |
Creacional |
Prototype |
por creador* |
secuencia 3 |
"poeta" fotocopiador |
Creacional |
Builder |
por creador* (opcional) |
secuencia 3 |
chef de menúes |
Comportamiento |
Mediator |
por centralización (opcional) |
arbol 2 |
árbitro en partido |
Creacional |
Singleton |
(opcional) |
no ha lugar |
guarda jurado en empresa |
|
Relaciones entre Patrones de Diseño
Aplicación de Patrones de Diseño
Aplicaciones | ||
---|---|---|
Sintesis
Bibliografía
Obra, Autor y Edición | Portada | Obra, Autor y Edición | Portada |
---|---|---|---|
|
|
|
|
Ponente
|
|
|