Trabajo de Patrones Graft
Trabajo de Patrones Graft
Trabajo de Patrones Graft
Integrantes:
BRAHIAN STIVEN MARTINEZ CERVERA
Ibagué-Tolima
2018
GRASP
En diseño orientado a objetos, GRASP son patrones generales de software para asignación de
responsabilidades, "GRASP (object-oriented design General Responsibility Assignment Software
Patterns)". Aunque se considera que más que patrones propiamente dichos, son una serie de "buenas
prácticas" de aplicación recomendable en el diseño de software.
Antes de iniciar hablando de los patrones grasp daremos a conocer las soluciones y los
beneficios de los grasp además de saber cuál es su función.
Una de las cosas más complicadas en Orientación a Objeto consiste en elegir las clases
adecuadas y decidir cómo estas clases deben interactuar. Incluso cuando utilizamos
metodologías rápidas como programación extrema (extreme programming) y centramos el
proceso en el desarrollo continuo, es inevitable elegir cuidadosamente las responsabilidades
de cada clase en la primera codificación y, fundamentalmente, en la refactorización
(continual) de nuestro programa.
Lo patrones de GRASP, no compiten con los patrones de diseño…. Los patrones de GRASP, nos
guían para ayudarnos a encontrar los patrones de diseño
PATRONES
BENEFICIOS BENEFICIOS
Mantenibilidad
Extensibilidad
Reestructuracion
Portabilidad
DA A CONOCER
Información privada
Objetos relacionados
Lo que puede derivar/calcular
Ej: M étodos analizadores "get"
HACER
Ejecuta un cálculo
Crea un objeto
Iniciar acciones en otros Objetos
Controlar/Coordinar actividades en otros Objetos
Ej: Métodos modificadores "set"
EXPERTO
La responsabilidad de realizar una labor es de la clase que tiene o puede tener los datos
involucrados (atributos). Una clase, contiene toda la información necesaria para realizar la
labor que tiene encomendada.
Hay que tener en cuenta que esto es aplicable mientras estemos considerando los mismos
aspectos del sistema:
Lógica de negocio
Persistencia a la base de datos
Interfaz de usuario
Problema:
Discusión:
Expertos de información parcial
Suele conducir a diseños donde los objetos se hacen responsables de las mismas
operaciones de los objetos inanimados a los que representan
Analogía en el mundo real. Normalmente se otorgan responsabilidades a los individuos
que pueden disponer de toda la información necesaria para llevar a cabo una tarea
Contraindicaciones
En ocasiones su aplicación puede aumentar el acoplamiento y reducir la cohesión
¿quién debería de almacenar una Venta en la BBDD?
-Siguiendo el EI se decidiría por la misma clase Venta. Esto podría llevar a una
situación en que cada clase tenga la responsabilidad de acceder a la BBDD
» La clase no está centrada únicamente en la lógica
» Todas las clases estarían acopladas con las diferentes clases de acceso a
BBDD
» Probablemente se duplicaría mucho código
Solución:
BENEFICIO
Se mantiene el encapsulamiento de la información con -> menor acoplamiento
Se distribuye el comportamiento entre las clases que contienen la información requerida
Con -> Mayor cohesión
Conserva el Encapsulamiento
Bajo Acoplamiento
Alta Cohesión
Patrones relacionados
Bajo acoplamiento y alta cohesión
Ejemplo
Asociaciones de Venta
Calculo Total de la Venta
Métodos a implementar
CREADOR
El hecho de crear objetos tiene casuísticas particulares:
Pool de Objetos
Caches
Instancias únicas
Estos casos son candidatos para la utilización de otros patrones más concretos (de diseño).
A la hora de crear objetos en distintos lenguajes hay que tener en cuenta sus peculiaridades.
En Java por ejemplo:
Los tags son reciclados en JSP (ojo con valores anteriores de atributos de
clase).
Problema
¿Quién debe ser el responsable de la creación de una nueva instancia de una clase?
Discusión
La intención básica es encontrar un creador que necesite dialogar con el objeto creado en
algún momento
Se puede ver como un caso particular del Experto (cuando B tiene los datos de
inicialización de A).
♦ No es tan evidente, con frecuencia hay un objeto principal que
construye las partes y se las pasa al contenedor
Contraindicaciones
En ocasiones la creación posee una complejidad significativa resultando más conveniente
delegar esta responsabilidad en una clase diseñada a tal efecto
Beneficios
Favorece el bajo acoplamiento
Patrones relacionados
Bajo acoplamiento y alta cohesión
Fábricas
Solución
Asigne a la clase B la responsabilidad de crear una instancia de la clase A si se da alguna de las
siguientes circunstancias:
B agrega (compartida mente o no) a A
B tiene los datos de inicialización de A
B registra a A
B utiliza ‘estrechamente’ a A
Ejemplo
¿Quién debería ser responsable de la creación de una instancia de Línea De Venta?
Según este patrón, debería ser Venta
BAJO ACOPLAMIENTO
Para determinar el nivel de acoplamiento de clases, son muy buenos los diagramas de
colaboración de UML.
Uno de los principales síntomas de un mal diseño y alto acoplamiento es una herencia muy
profunda. Siempre hay que considerar las ventajas de la delegación respecto de la herencia.
Como ejemplo (de mal diseño), en muchos diseños Web se puede ver como se crea un servlet
base con capacidades de paginación y se hereda de él para construir los demás. La paginación
es un servicio que se podría usar en aplicaciones no Web, por lo que es más adecuado
mantener estas capacidades en clases externas.
Otro ejemplo clásico se produce cuando se pasan los objetos relacionados con la capa de
presentación a la capa de negocio (HttpRequest, HttpResponse).
Acoplamiento
Es una medida de la fuerza con que un elemento está conectado a, tiene conocimiento de, confía
en, otros elementos
Un elemento fuertemente acoplado
Se resiente de los cambios en los elementos relacionados
Son difíciles de entender de manera aislada
Difíciles de reutilizar (requiere las clases ‘acopladas’)
Problema
¿Cómo mantener un bajo acoplamiento para lograr, entre otras cosas, alta reutilización?
Nota: el acoplamiento mide el grado en que una clase está conectada a otra, tiene conocimiento
de otra o, de alguna manera, depende de otra.
Discusión
En la práctica, el nivel de acoplamiento no puede evaluarse si tener en cuenta otros GRASP
como la cohesión o el experto
Una subclase está fuertemente acoplada a su superclase. Esta decisión deber ser
estudiada cuidadosamente
No existe medida que indique si el acoplamiento es demasiado alto lo que debe tener en
cuenta el diseñador es el impacto que provoca una decisión en el grado de acoplamiento
Solución
Asigna responsabilidades de manera que el acoplamiento permanezca bajo
¿Es una solución?
lo aplica un diseñador cada vez que tiene que evaluar una decisión de diseño
Contraindicaciones
Escoger las batallas
El alto acoplamiento no es inherentemente malo, lo es sólo con elementos
“inestables” (en su contrato sintáctico, semántico, ... O su simple presencia)
– Una aplicación J2EE puede acoplarse con seguridad con la biblioteca java. Útil.
Beneficios
Se amortigua el impacto de los cambios en los elementos inestables
Se facilita el entendimiento
Facilita la reutilización
Antecedentes
Acoplamiento y cohesión son principios realmente fundamentales que fueron propuestos
por Larry Constantine a finales de los 60 (40 años…)
Patrones relacionados
Alta Cohesión
ALTA COHESION
Cada elemento de nuestro diseño debe realizar una labor única dentro del sistema, no
desempeñada por el resto de los elementos y auto-identificable.
Ejemplos de una baja cohesión son clases que hacen demasiadas cosas.
En java, pensar en interfaces nos fuerza a que nuestros sistemas sigan los principios de alta
cohesión. En algún sitio leí que una aplicación con menos de 8 interfaces no utiliza
correctamente los conceptos de orientación a objeto… y estoy de acuerdo….
Cohesión
Es una medida de la fuerza con la que se relacionan y del grado de focalización de las
responsabilidades de un elemento.
Un elemento con baja cohesión tiene muchas responsabilidades:
Difíciles de entender, reutilizar y mantener
Delicadas, frágiles (muchas probabilidades de verse afectada en los cambios)
.
Problema
¿cómo mantener la complejidad de una clase en niveles manejables?
Nota: la cohesión mide el grado en que están relacionadas las responsabilidades de una clase.
Discusión
Una clase altamente cohesiva suele:
tener un número relativamente pequeño de métodos, con funcionalidad altamente
relacionada
no realiza mucho trabajo
Colabora con otras clases para compartir el esfuerzo
Una de las posibles analogías en el mundo real
Un trabajador con muchas responsabilidades (que no mucha responsabilidad) suele
ser poco efectivo
Diseño modular
Cohesión y acoplamiento son principios conocidos desde hace mucho. A partir de ellos
se define el principio de modularidad
Un diseño es modular si se descompone en un conjunto de módulos
cohesivos y débilmente acoplados
Cohesión y acoplamiento: el ying y el yang de la IS
Una mala cohesión causa, normalmente, un mal acoplamiento
La filosofía china los considera como fuerzas opuestas pero complementarias
Un buen diseño siempre logra un buen equilibrio entre cohesión y acoplamiento
BENEFICIO
Mejoran la claridad del Diseño
Simplificación del cambio
Genera bajo acoplamiento
Facilita la reutilización
Solución
asignar responsabilidades de manera que la cohesión se mantenga alta.
Contraindicaciones
Existen pocas situaciones que justifiquen la aceptación de una baja cohesión (Fachadas)
Beneficios
Se incrementa claridad y comprensión
Se simplifica el mantenimiento
Favorece el bajo acoplamiento
Facilita la reutilización
Patrones relacionados
Bajo acoplamiento
Ejemplo
Alta Cohesion
Baja Cohesion
CONTROLADOR
Asignar la responsabilidad de controlar el flujo de eventos del sistema, a clases específicas.
Esto facilita la centralización de actividades (validaciones, seguridad, etc.). El controlador no
realiza estas actividades, las delega en otras clases con las que mantiene un modelo de alta
cohesión.
Un error muy común es asignarle demasiada responsabilidad y alto nivel de acoplamiento con
el resto de los componentes del sistema.
Hay otros muchos patrones relacionados sobre todo en entornos multi-capa (Core J2EE
patterns).
Problema
¿Quién debería ser responsable de manejar un evento del sistema?
Nota: Un evento del sistema es un evento generado por un actor externo.
Sugerencias
Puede utilizarse un controlador por cada CdU, de manera que se encargue de
controlar el estado del CdU, la secuencia de eventos, etc.
Una vez que el controlador recibe un evento, puede delegar sobre otros objetos para
no verse sobrecargado (producirá, además, baja cohesión).
BENEFICIO
Mayor potencial de los Componentes reutilizables
Solución
Asignar la responsabilidad al “controlador”, que será una clase que:
Representa al sistema completo, a la organización... (controlador “fachada”)
Representa una parte activa del mundo real que desencadena de tal evento
(controlador de rol)
Representa un manejador artificial de eventos (controlador de CdU)
Ejemplo
Opciones de Controlador
Solución Deseable
No muy buena Solución
FABRICACION PURA
Cuando los problemas se complican, construir clases que se encarguen de construir los
objetos adecuados en cada momento (factorías).
Problema
¿Qué objetos deberían tener la responsabilidad cuando no se quiere violar los objetivos de alta
cohesión y bajo acoplamiento?
Consecuencias
Se soporta alta cohesión puesto que las responsabilidades se factorizan en una clase de
grano fino.
El potencial para reutilizar aumenta.
Beneficios
Las fabricaciones puras suelen ser muy cohesivas y reutilizables
Ejemplo
Se necesita soporte para almacenar la Venta en una base de datos se podría suponer
según el Experto que la propia clase debería tener la responsabilidad.
La tarea implica un número amplio de operaciones de bases de datos no relacionadas con
las ventas (Baja cohesión).
Además tiene que acoplarse con el interfaz de la base de datos. 8
Y por último la tarea de almacenar objetos puede ser necesaria para más objetos.
Podríamos crear una clase Almacenamiento Persistente.
Contraindicaciones
El uso desmedido de fabricaciones puras puede derivar en clases “función” o
“algoritmo” (sólo tienen un método).
Patrones relacionados
Bajo Acoplamiento, Alta Cohesión, los GoF (Adaptador, Command, Estrategia, etc.) y
prácticamente el resto de patrones de diseño.
Solución
Asigna un conjunto de responsabilidades altamente cohesivas a una clase artificial que no
representa un concepto del dominio.
INDIRECCION
Crear clases intermedias para desacoplar clientes de servicio y servicios.
Pensar en sistemas middleware y se verá la utilidad de un modo inmediato.
Problema
¿Dónde asignar una responsabilidad para evitar el acoplamiento directo entre dos objetos?
Consecuencias
Disminuye el acoplamiento entre componentes.
Discusión
“La mayoría de los problemas en diseño se resuelven mediante indirección”
La mayoría de los problemas en ejecución se pueden resolver eliminando
alguna indirección
Beneficios
Disminuye el acoplamiento
Patrones relacionados
Variaciones protegidas
Bajo Acoplamiento
Muchos patrones GoF usan indirección: adaptador, fachada, puente, observador,
mediador
Solución:
Asigne la responsabilidad a un objeto intermedio que medie entre otros componentes
para que no estén acoplados directamente, el intermediario crea un indirección.
Ejemplos
Muchos de los patrones existentes son especializaciones de Indirección.
Está relacionado con Variaciones Protegidas, Bajo Acoplamiento, muchos GoF como
Adaptador, Puente, Fachada, Observador y Mediador.
POLIMORFISMO
Problema
¿Cómo manejar alternativas basadas en el tipo? ¿Cómo crear componentes conectables?
Consecuencias
Se añaden fácilmente extensiones necesarias para nuevas variaciones.
Las nuevas implementaciones se introducen sin afectar a los clientes.
Patrones relacionados
Variaciones Protegidas, varios de los patrones de diseño GoF como Adaptador, Command,
Composite, Proxy, Estado y Estrategia.
Solución
Cuando el comportamiento relacionado varía según el tipo (clase) asigne la
responsabilidad para el comportamiento utilizando operaciones polimórficas a los
tipos para los que varía el comportamiento.
De sí mismo (this)
De su área de parámetros
Un objeto creado en su propio ámbito
(los demás los doy por incluidos)
Aunque pueden parecer muy evidentes los principios anteriormente enumerados, estaréis de
acuerdo conmigo que es muy complicado llevarlo a cabo en proyectos reales. Hay varios
factores que lo hace difícil:
La presión del día a día por producir resultados (aunque sean de poca calidad).
La planificación de proyectos a coste fijo (y a precios bajos) que quita las ganas de
pararse a pensar (más con 50 horas semanales de trabajo)
La poca inversión en formación de muchas empresas (modelo de servicios puro,
propiciado los últimos años)
Falta de personas de referencia (que nos enseñen y aprendamos juntos) en los
equipos de desarrollo
Estos patrones se utilizan cuando debemos crear objetos pero debemos tomar decisiones
dinámicamente en el proceso de creación. Para esto lo que hacemos es abstraer el proceso de
creación de los objetos para realizar la decisión de qué objetos crear o cómo crearlos para el
momento en que se tenga que hacer.
El objetivo principal de los patrones es facilitar la reutilización de diseños y arquitecturas software
que han tenido éxito capturando la experiencia y haciéndola accesible a los no expertos.
Beneficios
Proporcionan elementos reusables en el diseño de sistemas software, lo que significa
que es aplicable a diferentes problemas de diseño en distintas circunstancias.
Efectividad comprobada en la resolución de problemas similares en ocasiones
anteriores.
Formalizan un vocabulario común entre diseñadores.
Estandarizan el diseño, lo que beneficia notablemente a los desarrolladores.
Facilitan el aprendizaje de las nuevas generaciones de diseñadores y desarrolladores
utilizando conocimiento ya existente.
Dentro de los patrones clásicos tenemos los GoF (Gang of Four), estudiados por Erich Gamma,
Richard Helm, Ralph Johnson y John Vlissides en su mítico libro Design Patterns se contemplan 3
tipos de patrones:
Patrones de creación: tratan de la inicialización y configuración de clases y objetos
Patrones estructurales: Tratan de desacoplar interfaz e implementación de clases y
objetos
Patrones de comportamiento tratan de las interacciones dinámicas entre sociedades
de clases y objetos
Patrones creacionales
Son los que facilitan la tarea de creación de nuevos objetos, de tal forma que el proceso
de creación pueda ser desacoplado de la implementación del resto del sistema.
Los patrones creacionales están basados en dos conceptos:
Abstract Factory
Nos provee una interfaz que delega la creación de un conjunto de objetos relacionados sin
necesidad de especificar en ningún momento cuáles son las implementaciones concretas.
Ventajas:
Aísla las clases de implementación: ayuda a controlar los objetos que se creen y encapsula la
responsabilidad y el proceso de creación de objetos producto.
Hace fácil el intercambio de familias de productos. Solo necesitaremos cambiar de factory.
Fomenta la consistencia entre productos.
Desventajas:
Para añadir un nuevo producto, se requiere la implementación del interfaz y todos sus métodos.
Y por fin hemos llegado al código. Vamos a implementar una pequeña imprenta y los servicios que
esta ofrece: Impresión a color, impresión en blanco y negro, y diseño de carteles.
Para ello vamos a implementar en primer lugar nuestro interfaz que implementarán nuestros
diferentes servicios
Factory Method
Expone un método de creación, delegando en las subclases la implementación de este
método.
Se define como una interfaz para la creación de cierto tipo de objeto, permitiendo que las
subclases decidan que clase concreta necesitan instanciar. El problema que se plantea en algunos
entornos es que una clase no puede anticipar el tipo de objetos que debe crear debido a la
jerarquía de clases existente, lo cual provoca que tenga que delegar esta tarea en una subclase.
Builder
Este patrón consiste en hacer una separación de la construcción de objetos complejos o
compuestos de su representación de modo que el mismo proceso de construcción pueda crear
diferentes representaciones.
Principalmente se usa para evitar situaciones de acoplamiento. Para el que no esté familiarizado
con este término, el acoplamiento define el nivel de dependencia entre clases y como buena
práctica de desarrollo siempre se tiende a intentar alcanzar un nivel bajo de acoplamiento si es
posible. El nivel más bajo de acoplamiento es cuando dos clases pueden funcionar una sin la otra
de forma independiente. El nivel más alto, es cuando una clase no puede funcionar sin la otra,
Por ejemplo, cuando una clase necesita acceder a un dato contenido en la otra. Como nos
estamos yendo un poquito por las ramas, si no tenéis suficiente con lo descrito aquí os
recomiendo que busquéis sobre factores que permiten mejorar la programación como:
acoplamiento, modularidad, cohesión…
Este patrón de se compone de:
Builder: Especifica la interfaz abstracta para crear las partes de un objeto Product.
ConcreteBuilder: Construye y monta las partes de un producto a través de la
implementación de la interfaz Builder, define y mantiene el registro de la representación
que se crea y proporciona una interfaz para recuperar el Producto.
Director: Construye el objeto usando la interfaz Builder
Product: Representa al objeto complejo en construcción. El ConcreteBuilder construye la
representación interna del producto y define el proceso con el que será montado.
Además, incluye las clases que definen las partes que constituyen el objeto (no olvidemos
que es un objeto complejo) incluyendo las interfaces para montar el resultado final.
Al final los pasos para la correcta utilización del patrón son los siguientes:
El Cliente crea un objeto Director y lo configura con el objeto Builder.
El Director notifica al constructor cuando una parte del Producto se debe construir.
El Builder maneja las peticiones del Director y agrega las partes al Producto.
El Cliente el producto del Builder.
Como consecuencias de la implementación del patrón tenemos:
Se nos permite variar la representación interna del Producto: como el producto se
construye a través de una interfaz abstracta, para cambiar la representación interna del
producto basta con definir un nuevo tipo de constructor.
Se nos permite separar el código de la construcción y la representación.
Nos da un control muy fino sobre el proceso de construcción de un Producto.
Singleton
Limita a uno el número de instancias posibles de una clase en nuestro programa, y
proporciona un acceso global al mismo.
El objetivo de este patrón es asegurarse de que de una clase solo existe una instancia y que esta es
accesible, o mejor dicho, ofrecer un punto de acceso a ella.
Existen muchas clases para las cuales es importante tener únicamente una sola instancia que
pueda ser utilizada en muchas partes diferentes del sistema.
Por ejemplo, por muchas impresoras que haya en nuestro sistema lo interesante es tener una sola
cola de impresión que maneje todas las impresiones. Otro ejemplo podría ser un gestor de
ventanas de una aplicación, ya que no vamos a tener varios gestores de ventanas para una misma
aplicación.
Algunos de los beneficios que nos aporta el patrón son:
Poder controlar el acceso a la instancia.
Reduce el espacio de nombres ya que evita contaminarlo con variables globales.
Permite refinar operaciones y la representación a través de la creación de subclases.
Permite controlar fácilmente y sin apenas cambios el número de instancias que creamos.
Sé que el patrón está enfocado a tener una sola instancia, pero en determinadas
circunstancias quizás necesitamos dos o tres por ejemplo.
Prototype
Permite la creación de objetos basados en “plantillas”. Un nuevo objeto se crea a partir
de la clonación de otro objeto.
Este patrón tiene como objetivo la especificación de un tipo de objeto que será un prototipo para
la creación de nuevos objetos copiando este, o mejor dicho, clonando este prototipo.
Una explicación más informal de esto sería algo así como, construir una jerarquía de clases en las
cuales, el primer objeto que se crea sea el prototipo, y a partir de este momento, no se van a crear
más nuevos objetos, sino que se van a clonar estos a partir del primero. Por aproximarlo un poco a
la programación, aunque luego veremos un ejemplo, nuestra aplicación hará un “new” del objeto
prototipo y a partir de ahí, el esto de objetos se obtendrán por clonación.
Aunque ya hemos adelantado una cuantas, las consecuencias de la utilización de este patrón
son las siguientes:
Nos permitirá crear o borrar productos en tiempo de ejecución.
La especificación de objetos nuevos variando sus valores.
La especificación de objetos nuevos variando su estructura.
Reduce el número de subclases respecto al resultado si no utilizáramos este patrón o si
utilizáramos otros como Factory Method para los casos que cubre Prototype.
Nos permite la configuración de una aplicación con clases dinámicas.
Patrones estructurales
Los patrones estructurales
especifican la forma en que unas
clases se relacionan con otras.
Adapter
Permite a dos clases con diferentes interfaces trabajar entre ellas, a través de un objeto
intermedio con el que se comunican e interactúan.
El patrón Adapter permite de esta forma trabajar juntas a clases que de otra forma no podrían por
la incompatibilidad de sus interfaces. Básicamente, y en palabras mucho más llanas, se conoce
como Wrapper, que no es más que una envoltura sobre algún objeto en concreto para que
podamos utilizarlo desde puntos donde no estaba pensado antes.
De esta forma objetos que ya tenemos implementados pueden ser adaptados para ser
reutilizados sin que se necesiten cambios en ellos, simplemente envolviéndolos con una
capa alrededor que nos permitirá adaptarlos al nuevo interfaz deseado. Pero como
siempre, lo más fácil es ver esto con un ejemplo, así que sigamos avanzando.
Este patrón se compone de los siguientes elementos:
Target: Define el dominio específico de la interfaz que el cliente va a utilizar.
Client: Maneja los diferentes objetos según le permite el interfaz Target.
Adaptee: Define un interfaz existente que necesita ser adaptado.
Adapter: Adapta el interfaz de Adaptee al interfaz usado por el cliente Target.
Al final, el comportamiento resumido de esto, será que el cliente llamará a métodos del
interfaz Adapter que a su vez llamarán a métodos del objeto Adaptee.
Bridge
Desacopla una abstracción de su implementación, para que las dos puedan evolucionar de
forma independiente.
El objetivo de este patrón es la separación (desacoplamiento) de una abstracción de su
implementación, de tal forma que ambas puedan ser modificadas independientemente sin a
afectar la una a la otra. Bien, ¿y qué significa esto? Porque seguro que os lo estáis preguntando o
al menos, yo sí que lo hice la primera (segunda y tercera) vez que leí la frase. Por ponerlo en
palabras más entendible o intentar explicar la idea, vamos a comentar primero el caso habitual.
Composite
Facilita la creación de estructuras de objetos en árbol, donde todos los elementos
emplean una misma interfaz. Cada uno de ellos puede a su vez contener un listado de
esos objetos, o ser el último de esa rama.
El objetivo de este patrón es el de montar los objetos en una estructura de árbol para
representarlos de una forma jerarquizada. De este modo, se podrán manejar de forma similar y
uniforme, tanto los objetos individuales como los objetos compuestos.
Este patrón es aplicable en situaciones es las que se necesita trabajar simultáneamente con
elementos simples y colecciones que contienen elementos simples u otras colecciones,
obteniendo de esta forma la estructura de árbol comentada. Un ejemplo muy utilizado para hacer
ver la necesidad y aplicabilidad de este patrón es la implementación de un editor gráfico en el que
podemos encontrar multitud de figuras y, además, escenas compuestas por muchas figuras.
Este patrón lo podemos aplicar en las siguientes situaciones:
Representar jerarquias de objetos simples y compuestos que puedan ser manejados de
forma uniforme.
Permitir a los clientes ignorar las diferencias entre objetos individuales o composiciones de
objetos.
Los elementos implicados en este patrón son:
Component: Declara la interfaz para objetos de la composición, implementa el
comportamiento por defecto que será común a todas las clases, declara la instancia para
el acceso y manejo de los componentes hijos y, opcionalmente, define la interfaz para
acceder a los componentes padres en una estructura recursiva e implementa esta última si
es necesario.
Leaf: Representa los objetos simples de la composición. Estos objetos no tiene más hojas.
Además, define el comportamiento de estos objetos primitivos.
Composite: Define el comportamiento para componentes que tienen hijos, almacena
objetos simples de la composición e implementa el comportamiento relacionado con los
hijos en el “Component”.
Client: Manipula los objetos de la composición a través de los métodos del “Component”.
Como en todos los casos anteriores, la aplicación del patrón tiene sus fortalezas, pero también
provoca algunos pequeñas pegas como que puede hacer el diseño sea demasiado general y, en
ocasiones, que sea difícil controlas los elementos que se incluyen en las diferentes composiciones
y pertenecen a ellas. Para paliar este problema, se pueden incluir controles en tiempo de
ejecución para poder controlarlo.
Decorator
Permite añadir funcionalidad extra a un objeto (de forma dinámica o estática) sin
modificar el comportamiento del resto de objetos del mismo tipo.
Facade
Una facade (o fachada) es un objeto que crea una interfaz simplificada para tratar con
otra parte del código más compleja, de tal forma que simplifica y aísla su uso. Un ejemplo
podría ser crear una fachada para tratar con una clase de una librería externa.
Patrón Facade, que básicamente nos ayuda a crear o nos provee de una interfaz unificada
para manejar un conjunto de objetos en un subsistema. De esta forma, estaremos
definiendo un interfaz de alto nivel que hará los subsistemas más fáciles de manejar.
La estructuración de un sistema en pequeños subsistemas permite reducir la complejidad
de dicho sistema, el problema es que muchas veces crea muchas dependencias entre los
subsistemas implementados. Es en este caso donde el patrón Facade nos puede ayudar
creando una única interfaz simple que nos permita acceder a las funcionalidades de
nuestros subsistemas.
El patrón Facade se puede aplicar:
Cuando se quiere proveer de una interfaz simple para un conjunto complejo de
subsistemas. Por ejemplo, la aplicación de muchos patrones de diseño provoca la
creación de muchas clases muy pequeñas y reutilizables, pero este nivel de
granularidad hace difícil el manejo del sistema, aquí podríamos aplicar este patrón.
Cuando hay muchas dependencias entre los clientes y el sistema. Aplicando el
patrón podemos aumentar el nivel de desacoplamiento aumentando así la
independencia y la portabilidad.
Cuando quieres introducir capas en tus subsistemas, usando el patrón para definir
un punto de entrada. Además, si los subsistemas son muy dependientes, podemos
simplificar esta dependencia haciendo que estos se comuniquen a través de sus
Facades.
Los elementos que componen este patrón son:
Facade: Conoce los subsistemas implicados en el sistema global, accede a ellos y
les envía las peticiones de los clientes.
Subsystem classes: Implementan la funcionalidad del sistema, realizan las tareas
enviadas por el Facade y no tienen referencias a Facade.
Con todo esto, las consecuencias de la aplicación de este patrón son una reducción del
número de objetos que tienen que manejar los clientes ya que, en vez de necesitar un
objeto por cada subsistema implicado, solo necesitan el objeto Facade. Esto último
provoca un acoplamiento más débil entre el sistema y los clientes, con lo cual hace más
fácil los cambios o modificaciones en el sistema y sus subsistemas, permitiéndonos
también menos acoplamiento entre nuestros subsistemas mejorando las dependencias de
compilación, cosa muy importante en grandes sistemas.
Flyweight
Una gran cantidad de objetos comparte un mismo objeto con propiedades comunes con el
fin de ahorrar memoria.
Implementación:
Crear una clase PelotaFlyweight, que contendrá la información común (radio y color) y
otra clase PelotaConcreta, que contendrá las coordenadas concretas de cada pelota y una
referencia a un objeto de tipo PelotaFlyweight.
Al crearse instancias de PelotaConcreta, se les deberá proveer de referencias a la instancia
de PelotaFlyweight adecuada a nuestras necesidades.
Ventajas y desventajas:
Ventajas: Reduce en gran cantidad el peso de los datos en un servidor
Desventajas: Consume un poco más de tiempo para realizar las búsquedas
Proxy
Es una clase que funciona como interfaz hacia cualquier otra cosa: una conexión a
Internet, un archivo en disco o cualquier otro recurso que sea costoso o imposible de
duplicar.
Aplicabilidad:
El patrón proxy se usa cuando se necesita una referencia a un objeto más flexible o
sofisticado que un puntero. Dependiendo de la función que se desea realizar con dicha
referencia podemos distinguir diferentes tipos de proxis:
Consecuencias:
El uso de un proxy introduce un nivel de indirección adicional con diferentes usos:
Además, su uso también permite realizar una optimización COW (copy-on-write), puesto
que copiar un objeto grande puede ser costoso, y si la copia no se modifica, no es
necesario incurrir en dicho gasto. Además el sujeto mantiene un número de referencias, y
sólo cuando se realiza una operación que modifica el objeto, éste se copia. Es útil por
tanto para retrasar la replicación de un objeto hasta que cambia.
Patrones de comportamiento
Command
Son objetos que encapsulan una acción y los parámetros que necesitan para ejecutarse.
Propósito:
Encapsular peticiones en forma de objetos.
Permite parametrizar a los clientes con distintas peticiones, hacer colas de peticiones,
llevar un registro de las peticiones realizadas, y deshacer el efecto de las peticiones.
También conocido como action, transacción
Aplicabilidad:
Usa el patrón Command si quieres:
Parametrizar objetos mediante una acción
Especificar, encolar y ejecutar peticiones en distintos momentos (el objeto command tiene
un tiempo de vida distinto de la petición)
Operaciones deshacer (método adicional en command)
Acciones de recuperación del sistema (métodos adicionales salvar y recuperar en
command)
Interfaz común que permita invocar las acciones de modo uniforme, y extender el sistema
con nuevas acciones de forma sencilla
Participantes:
Command: define la interfaz de ejecución de operaciones
ConcreteCommand (PasteCommand, OpenCommand): Implementa la interfaz de
ejecución invocando operaciones del receptor. De este modo relaciona una acción con un
receptor
Client (Application): crea un comando concreto e indica a quién va dirigido (receptor)
Invoker (MenuItem): contiene el comando asociado a la petición
Receiver (Document, Application): sabe cómo realizar las operaciones asociadas a una
petición. Cualquier clase puede actuar como receptor.
Consecuencias
Desacopla el objeto que invoca la operación del que sabe cómo llevarla a cabo
Los comandos son entidades de “primer orden”: se pueden manipular y extender como
cualquier otro objeto
Se pueden crear macro-comandos (patrón composite)
Es fácil añadir nuevos comandos ya que no es necesario cambiar las clases existentes
Chain of responsibility
Se evita acoplar al emisor y receptor de una petición dando la posibilidad a varios
receptores de consumirlo. Cada receptor tiene la opción de consumir esa petición o
pasárselo al siguiente dentro de la cadena.
Aplicabilidad
El patrón Cadena de Responsabilidad debe usarse cuando:
Hay más de un objeto que puede manejar una petición, y el manejador no se
conoce a priori, sino que debería determinarse automáticamente.
Se quiere enviar una petición a un objeto entre varios sin especificar
explícitamente el receptor.
El conjunto de objetos que pueden tratar una petición debería ser especificado
dinámicamente.
Participantes
Manejador: define una interfaz para tratar las peticiones. Opcionalmente,
implementa el enlace al sucesor.
ManejadorConcreto: trata las peticiones de las que es responsable; si el
ManejadorConcreto puede manejar la petición, lo hace; en caso contrario la
reenvía a su sucesor.
Cliente: inicializa la petición a un Manejador Concreto de la cadena.
Consecuencias
Reduce el acoplamiento. El patrón libera a un objeto de tener que saber qué otro objeto
maneja una petición. Ni el receptor ni el emisor se conocen explícitamente entre ellos, y
un objeto de la cadena tampoco tiene que conocer la estructura de ésta. Por lo tanto,
simplifica las interconexiones entre objetos. En vez de que los objetos mantengan
referencias a todos los posibles receptores, sólo tienen una única referencia a su sucesor.
Añade flexibilidad para asignar responsabilidades a objetos.
Patrones Relacionados
Este patrón se aplica en ocasiones con el patrón Composite. En él, los padres de los
componentes pueden actuar como sucesores.
Interpreter
Define una representación para una gramática así como el mecanismo para evaluarla. El
árbol de sintaxis del lenguaje se suele modelar mediante el patrón Composite.
El patrón de diseño interpreter es utilizado para evaluar un lenguaje definido como
Expresiones, este patrón nos permite interpretar un lenguaje como Java, C#, SQL o incluso
un lenguaje inventado por nosotros el cual tiene un significado; y darnos una respuesta
tras evaluar dicho lenguaje.
Aplicabilidad
Cuando tratamos con gramáticas simples, en caso contrario la mejor opción es
utilizar parsers.
La eficiencia no es uno de los aspectos más importantes. Hay que traducir el input
a una forma inmediata.
Participantes:
ExpresiónAbstracta: Declara una operación abstracta Intérprete que es común a
todos los nodos del árbol de sintaxis abstracta.
ExpresiónTerminal: Una instancia es requerida por cada aparición en una
sentencia. implementa un operación Intérpreteasociada a cada símbolo terminal.
ExpresiónNoTerminal: Para cada regla es necesario un tipo de clase.
Cliente: Construye el árbol sintáctico de ExpresionesNoTerminales, e instancias de
la clase ExpresiónTerminal.
Contexto: Contiene información global para el interpretador.
Colaboraciones:
Cliente construye una sentencia compuesta
de ExpresionesTerminales y ExpresionesNoTerminales. Luego inicializa el Contexto e invoca
al interpretador
Cada nodo correspondiente a ExpresionesNoTerminales define al interpretador en
función de subexpresiones
Las operaciones en cada nodo utilizan el Contexto para almacenar y acceder al
estado del interpretador
Consecuencias:
Es fácil cambiar y extender la gramática.
Implementar la gramática es sencillo.
Las gramáticas complejas son difíciles de mantener.
Se puede añadir nuevas interpretaciones de los símbolos.
Implementación:
Para realizar una correcta implementación del patrón es recomendable:
La creación del árbol sintáctico no se especifica. Se puede usar un parser o
realizarlo en cliente
Definiendo la operación de intérprete. No hace falta implementarla en cada clase
de expresiones. Es recomendable el uso del patrón Visitante.
Compartiendo símbolos terminales mediante el patrón Peso Ligero.
Iterator
Se utiliza para poder movernos por los elementos de un conjunto de forma secuencial sin
necesidad de exponer su implementación específica.
Propósito:
Proporcionar acceso secuencial a los elementos de un agregado, sin exponer su
representación interna
También conocido como cursor
Motivación:
Generalizar el iterador para que soporte iteración polimórfica
Se puede cambiar el agregado sin cambiar el código cliente
Las listas se hacen responsables de crear sus propios iteradores
Aplicabilidad:
Usa el patrón Iterator:
Para acceder al contenido de un agregado sin exponer su representación interna
Para permitir varios recorridos sobre un agregado
Para proporcionar una interfaz uniforme para recorrer distintos tipos de agregados (esto
es, permitir iteración polimórfica)
Mediator
Objeto que encapsula cómo otro conjunto de objetos interactúan y se comunican entre sí.
Consecuencias:
Desacopla a los colegas: el patrón Mediator promueve bajar el acoplamiento entre
colegas. Se puede variar y reusar colegas y mediadores independiéntemente.
Simplifica la comunicación entre objetos: los objetos que se comunican de la forma
"muchos a muchos" puede ser remplazada por una forma "uno a muchos" que es
menos compleja y más elegante. Además esta forma de comunicación es más fácil
de entender. Es decir, un objeto no necesita conocer a todos los objetos, tan sólo a
un mediador.
Clarifica como los objetos se relacionan en un sistema.
Centraliza el control: el mediador es el que se encarga de comunicar a los colegas,
este puede ser muy complejo, difícil de entender y modificar. Para que quién
conoce el framework Struts, es muy similar al concepto del archivo struts-
config.xml: centraliza el funcionamiento de la aplicación, aunque si llega a ser una
aplicación muy compleja el archivo se vuelve un tanto complicado de entender y
seguir.
Memento
Este patrón otorga la capacidad de restaurar un objeto a un estado anterior
Memento es un patrón diseñado que nos permite capturar el estado de un objeto en un
momento determinado con la finalidad de regresar a este estado en cualquier momento.
Este patrón es utilizado cuando tenemos objetos que cambian en el tiempo y por alguna
razón necesitamos restaurar su estado en un momento determinado.
Aplicabilidad:
El patrón Memento es aplicable cuando:
Todo o parte del estado de un objeto debe ser guardado para ser restaurado más tarde.
Cuando una interfaz directa para obtener el estado de un objeto exponga detalles de su
implementación.
Participantes:
Memento.
Almacena el estado interno de un objeto Originator. El Memento puede almacenar mucho
o parte del estado interno de Originator.
Tiene dos interfaces. Una para Caretaker, que le permite manipular el Memento
únicamente para pasarlo a otros objetos. La otra interfaz sirve para que Originator pueda
almacenar/restaurar su estado interno, sólo Originator puede acceder a esta interfaz, al
menos en teoría.
Originator.
Crea un objeto Memento conteniendo una fotografía (un instante) de su estado interno.
Usa a Memento para restaurar su estado interno.
Caretaker:
Es responsable por mantener a salvo a Memento.
No opera o examina el contenido de Memento.
Colaboraciones
Un Carataker solicita un memento a un creador, lo almacena durante un tiempo y se lo
devuelve a su creador, tal y como muestra el diagrama.
A veces el Carataker no devolverá el memento a su creador, ya que el creador podría no
necesitar nunca volver a un estado anterior.
Los mementos son pasivos. Sólo el creador que creó el memento asignará o recuperará su
estado.
Observer
Los objetos son capaces de suscribirse a una serie de eventos que otro objetivo va a
emitir, y serán avisados cuando esto ocurra.
En Java este patrón podemos implementarlo usando una clase, la clase Observable, y una
interfaz, la interfaz Observer proporcionadas en el propio JDK. La clase que queremos que
reciba los eventos deberá implementar la interfaz Observer y el objeto que queremos que
produzca los eventos debe extender o contener una propiedad de tipo Observable. La
interfaz Observer contiene un único método de nombre update, que recibe dos
parámetros que son la instancia del objeto observable sobre la que se ha producido el
evento y un Object a modo de argumento que el objeto observable envía.
State
Permite modificar la forma en que un objeto se comporta en tiempo de ejecución,
basándose en su estado interno.
Ventajas:
Los estados son creados sólo una vez, y el estado corriente es sólo un puntero que
va a apuntar sobre uno de estos estados ya instanciados. Entonces, no hay ninguna
manipulación de memoria (new/delete), lo que es a menudo una buena cosa
cuando las clases estados concretos son un poco gordas.
Este método no impone limitaciones relacionadas con la responsabilidad de las
transiciones. En efecto, puede ser implementada en el contexto (es el caso en el
código más arriba), o dejada en los estados concretos.
Inconvenientes:
Esta implementación no es deseable cuando hay muchos estados de tipo
diferente.
Esta implementación implica una gestión bastante especializada de los estados (el
contexto debe conocer los diferentes tipos de estados). Esto puede hacer que sea
más difícil la modificación del diagrama de estado.
Strategy
Permite la selección del algoritmo que ejecuta cierta acción en tiempo de ejecución.
Propósito:
Definir una familia de algoritmos, encapsularlos y hacerlos intercambiables
Permite que el algoritmo cambie sin que afecte a los clientes que lo usan
También conocido como policy (política)
Participantes:
Las clases y objetos que participan en este modelo son:
Strategy
ConcreteStrategy
Implementa el algoritmo utilizando la interfaz de Strategy
Context
Está configurado con un objeto ConcreteStrategy
Mantiene una referencia a un objeto de Strategy
Puede definir una interfaz que le permite acceder a sus datos Strategy.
Aplicabilidad
Usa el patrón Strategy cuando:
Varias clases relacionadas sólo difieren en su comportamiento. Strategy permite
configurar a una clase con uno de entre varios comportamientos
Se necesiten variantes del mismo algoritmo, que se implementan como una jerarquía de
clases
Un algoritmo usa datos que los clientes no tienen por qué conocer (ej. estructuras de
datos específicas del algoritmo)
Una clase define muchos comportamientos que aparecen en sentencias condicionales →
mover los relacionados a un strategy
Participantes:
Strategy (Compositor): define una interfaz común a los algoritmos que soporta.
ConcreteStrategy (SimpleCompositor, TeXCompositor, ArrayCompositor): implementa un
algoritmo usando la interfaz Strategy
Context (Composition):
Está configurado con un objeto ConcreteStrategy
Mantiene una referencia al objeto Strategy
Puede definir una interfaz que le permita a Strategy acceder a sus datos
Template Method
Especifica el esqueleto de un algoritmo, permitiendo a las subclases definir cómo
implementan el comportamiento real.
Aplicaciones:
Se quiera implementar las partes de un algoritmo que no cambian y dejar que las
subclases implementan aquellas otras que puedan variar.
Por motivo de factorizar código, cuando movemos cierto código a una clase base
común evitar código duplicado.
Para controlar el modo en que las subclases extienden la clase base. Haciendo que
solo sea a través de unos métodos de plantilla datos.
El uso de este patrón genera una serie de consecuencias que tenemos que tener
presente:
Inversión de control: es la clase padre quien llama a las operaciones de los hijos.
Visitor
Permite separar el algoritmo de la estructura de datos que se utilizará para ejecutarlo. De
esta forma se pueden añadir nuevas operaciones a estas estructuras sin necesidad de
modificarlas.
Propósito:
Representa una operación a realizar sobre los elementos de una estructura de
objetos
Permite definir una nueva operación sin cambiar las clases de elementos sobre las
que opera
Aplicabilidad:
Usa el patrón Visitor cuando:
Una estructura de objetos contiene muchas clases de objetos con interfaces
distintas, y se quiere realizar sobre ellos operaciones que son distintas en cada
clase concreta
Se quieren realizar muchas operaciones distintas sobre los objetos de una
estructura, sin incluir dichas operaciones en las clases
Las clases que forman la estructura de objetos no cambian, pero las operaciones
sobre ellas sí
Participantes
Visitor (NodeVisitor): define una operación de visita para cada clase de elemento
concreto en la estructura de objetos
ConcreteVisitor (TypeCheckingVisitor):
Implementa la interfaz Visitor
Cada operación implementa un fragmento de la labor global del visitor concreto,
pudiendo almacenar información local
Element (Node): define una operación accept con un visitor como argumento
ConcreteElement (AssignmentNode): implementa la operación accept
ObjectStructure (Compiler):
Gestiona la estructura de objetos, y puede enumerar sus elementos
Puede ser un compuesto (patrón composite) o una colección de objetos
Puede ofrecer una interfaz que permita al visitor visitar a sus elementos
Consecuencias:
facilita la definición de nuevas operaciones
Agrupa operaciones relacionadas
Permite visitar objetos que no están relacionados por un padre común
El visitor puede acumular el estado de una operación al visitar la estructura de
objetos, en vez de pasarlo como argumento o usar variables globales
Añadir nuevas clases ConcreteElement es costoso
Utilizar el patrón visitor si la jerarquía de clases es estable
Rompe la encapsulació