Patrones de diseño

Command
Nombre del patrón: command –orden (también es conocido como Action y Transaction)
Clasificación del patrón: Patrones de comportamiento.
Problema:
Necesidad de enviar peticiones sin saber nada acerca de la operación solicitada ni del
recepetor de las mismas (toolkits de interfaces de usuario)


Cola de comandos (control de la secuencia)
Operaciones con históricos
Contexto: Implementación de un sistema basado en órdenes (comandos).
Prioridades:
Uniformidad al invocar acciones
Sencillez para extender el sistema con nuevas acciones.
Objetivos:
Cada elemento de menú que invoca una acción no debe conocer el método que
implementa ni la clase concreta que la implementa.
Encapsular la petición como un objeto, permitiendo parametrizar los clientes con distintas
peticiones, mantener una cola de peticiones y deshacer operaciones.
Estructura:
Participantes:
Orden:
Declara una interfaz para la ejecución de órdenes.
OrdenConcreta:
Define un enlace entre un objeto Receptor y una acción.
Implementa Ejecutar invocando la correspondiente operación u operaciones del Receptor.
Cliente:
Crea un objeto OrdenConcreta y establece su receptor.
Invocador:
Le pide a la orden que ejecute la petición
Receptor:
Sabe cómo llevar a cabo las operaciones asociadas a una petición. Cualquier clase puede
actuar como Receptor.
Colaboraciones:
 El cliente crea un objeto OrdenConcreta y especifica su receptor.
 Un objeto Invocador almacena el objeto OrdenConcreta.
 El Invocador envía una petición llamando a Ejecutar sobre la orden. Cuando las órdenes se
pueden Deshacer, OrdenConcreta guarda el estado para deshacer la orden antes de llamar
a Ejecutar.
 El objeto OrdenConcreta invoca operaciones de su receptor para llevar a cabo la petición.
Ventajas:
 Permite el desacoplamiento usando el objeto Orden ya que el invocador no es quien
realiza la acción.
 Se pueden crear operaciones que invoquen a varias acciones.
 Permite cambiar de forma dinámica las acciones que es capaz de invocar un menú.
 Se puede deshacer las acciones.
Inconvenientes:
Aumenta la complejidad al incrementar el número de clases (las cuales no tienen gran
responsabilidad)
Patrones relacionados
Composite: para agrupar órdenes.
Prototype: para copiar la orden al historial de órdenes.
Factory Method: forma alternativa de llamar a las órdenes
Memento: mantener el estado de los objetos para implementar la orden Deshacer.
Template: para implementar la lógica de Deshacer de forma automática.
Interpreter: se puede implementar un intérprete a través de órdenes command.
Ejemplo
publicinterface Orden {
voidejecutar();
}
publicclassInsertarCuadradoimplementsOrden{
Imagen imagen;
publicInsertarCuadrado(Imagen imagen){
this.imagen=imagen;
}
publicvoidejecutar() {
imagen.generarCuadrado();
}
}
publicclassMenuDibujo{
publicbooleanMenuPrincipal() {
...
case 2: //dibujar un cuadradoCommandcomando;
comando= new InsertarCuadrado(imagen);
comando.ejecutar();
break;
...
}
}
Composite
Nombre del patrón: Composite (Compuesto)
Clasificación del patrón:
Diseño Estructural de Objetos.
Problema
Estructuras de árbol o estructuras 1:N
Existencia de un objeto complejo que hay que descomponer en partes
Nodos especiales que pueden contener otros nodos
Contexto:
Las aplicaciones (gráficas, editores de dibujo, diseño de circuitos) donde los usuarios necesitan
construir diagramas complejos con componentes simples. Dichos componentes pueden ser
agrupados para formar otros más grandes, y estas a su vez pueden ser agrupadas.
El patrón Composite describe cómo usar una composición recursiva para no tener que distinguir
entre componentes simples y compuestos.
Objetivo:
Componer objetos en estructura de árbol para representar jerarquías de tipo “partestodo”.
Permitir que los clientes traten objetos individuales o primitivos y composiciones de objetos de
manera uniforme.
Participantes:
Componente (Gráfico):
Declara la interfaz de los objetos de la composición
Implementa el comportamiento predeterminado de la interfaz, común a todas las clases
Declara una interfaz para acceso y manipulación a sus componentes hijos
Define una interfaz para acceder al padre de un componente en la estructura recursiva (Opcional)
Hoja (Rectángulo, Línea, Texto):
Representa los objetos “Hoja” en la composición (no poseen hijos)
Define el comportamiento de los objetos primitivos
Composite (Dibujo)
Define el comportamiento de los componentes que tienen hijos
Almacena componentes hijos
Implementa las operaciones de la interfaz Componente relacionada con los hijos
Cliente
Manipula objetos de la composición a través de la interfaz Componente.
Colaboraciones
Los clientes usan la interfaz Componente para interactuar con los objetos de la estructura
Composite.
Si el receptor es una hoja, la interacción es directa y la petición se trata correctamente.
Si es un Compuesto, se debe llegar a los objetos “hijos”, entonces, normalmente se redirigen las
peticiones a sus componentes hijos, posiblemente utilizando operaciones adicionales.
Ventajas
Define jerarquía de clases formadas por objetos primitivos y compuestos
Simplifica el cliente. Tratan uniformemente estructuras compuestas y objetos individuales
Facilita añadir nuevos tipos de componentes
Inconvenientes
Hace el diseño demasiado general
Hay que tener más cuidado con las restricciones en los tipos de objetos que forman parte de un
compuesto
Hay que realizar comprobaciones dinámicas en tiempo de ejecución
Patrones relacionados
Chain of Responsibility. Hace uso del enlace al componente padre, para crear enlaces entre la
clase componente y su padre.
Decorator. Juntos, tienen una clase padre común, por lo que el patrón decorator tiene que admitir
la interfaz Componente con operaciones como Añadir, Eliminar y Obtener hijos.
FlyWeight. Permite compartir componentes, en cuyo caso ya no pueden referirse a sus padres.
Iterator. Para recorrer las estructuras definidas por el patrón Composite.
Visitor. Para localizar operaciones y comportamiento que de otro modo estarían distribuidas en
varias clases Compuesto y Hoja
Implementación:
1. Referencias explícitas a los padres.
Simplifica recorridos y la gestión de una estructura compuesta
Facilita ascender en la estructura y borrar un componente
¿Dónde? En la clase Componente
2. Compartir componentes.
Ahorra espacio de almacenamiento
Es difícil compartir cuando el componente tiene un solo padre
Solución Patrón Flyweight
3. Maximizar la interfaz Component.
Es bueno definir más operaciones posibles en la clase componente tanto para Compuesto como
para Hoja
Define operaciones por defecto en Componente y luego las subclases puedan redefinirlas si son
necesarias.
4. Declarar las operaciones de manejo de hijos.
En la raíz de la jerarquía (Componente)
Da transparencia y pierde seguridad
¿Cómo asegurarse de que no se le solicita una operación no permitida a un nodo hoja?
Ej. Añadir y eliminar objetos de las hojas
En la clase Compuesto
Da seguridad pero pierde transparencia.
Cualquier intento de añadir o eliminar objetos de las hojas se detecta en tiempo de compilación
Las hojas y los compuestos tienen interfaces diferentes
Implementación:
1. ¿Debería Componente implementar una lista de componentes?
Penaliza el espacio
Solo merece la pena si hay relativamente pocos hijos
2. Orden de los hijos.
Colaboración del patrón Iterator.
3. Caché para mejorar el rendimiento.
4. ¿Quién debería borrar los componentes?
Sin recolector de basura
Compuesto responsable de borrar sus hijos
5. ¿Cuál es la mejor estructura de datos para almacenar componentes?
Listas enlazadas, árboles, arrays y tablas de dispersión
Eficiencia
Ejemplo
Facade
Nombre del patrón: Facade, Façade (Fachada)
Clasificación del patrón: Estructural.
Intención:
Proporciona una interfaz unificada para un conjunto de interfaces en un subsistema.
Define una interfaz de alto nivel que hace que el subsistema sea más fácil de usar.
Motivación:
Estructurar un sistema en subsistemas ayuda a reducir la complejidad.
Minimizar la comunicación y las dependencias entre subsistemas.
Aplicabilidad:
Proporcionar una interfaz simple para un subsistema complejo.
Gran cantidad de dependencias entre los clientes y las clases que implementan una
abstracción.
Dividir los subsistemas en capas.
Estructura:
Participantes:
Fachada:
Sabe qué clases del subsistema son las responsables ante una petición.
Delega las peticiones de los clientes en los objetos apropiados del subsistema.
Clases del subsistema:
Implementan la funcionalidad del sistema.
Realizan las labores encomendadas por el objeto Fachada.
No conocen a la fachada, no tienen referencias a ella
Colaboraciones:
Los clientes se comunican con el subsistema enviando peticiones al objeto Fachada,
que las redirecciona a los objetos correspondientes del subsistema.
Los clientes que usan la fachada no tiene que acceder directamente a los objetos del
subsistema.
Consecuencias:
Oculta a los clientes los componentes del subsistema.
Promueve un débil acoplamiento entre el subsistema y los clientes.
No impide que las aplicaciones usen las clases del subsistema si es necesario.
Implementación:
Reducción del acoplamiento cliente-subsistema.
Clases del subsistema públicas o privadas.
Código de ejemplo:
import java.util.*;
/** "Facade" */
class UserfriendlyDate {
GregorianCalendar gcal;
public UserfriendlyDate(String isodate_ymd)
{
String[] a = isodate_ymd.split("-");
gcal = new GregorianCalendar(Integer.valueOf(a[0]).intValue(),
Integer.valueOf(a[1]).intValue()-1, Integer.valueOf(a[2]).intValue());
}
public void addDays(int days)
{
gcal.add(Calendar.DAY_OF_MONTH, days);
}
public String toString()
{
return new Formatter().format("%1$tY-%1$tm-%1$td", gcal).toString();
}
}
/** "Client" */
class FacadePattern {
public static void main(String[] args)
{
UserfriendlyDate d = new UserfriendlyDate("1980-08-20"); System.out.println("Date: "+d);
d.addDays(20); System.out.println("20 days after: "+d);
}
}
Usos conocidos:
Clases java.awt.Font y java.awt.Graphics.
Interfaces de librerias GIS o librerías matemáticas.
Patrones relacionados:
Abstract Factory
Mediator
Singleton
Method Factory
Nombre del patrón: Method Factory (Método Factoría)
Clasificación del patrón: De Creación.
Problema:
Creaciones de objetos por todo el código
Contexto:
Aplicaciones (frameworks) que se desean mantener flexibles y extendibles
Prioridades:
Flexibilidad.
No aumente complejidad.
Objetivo:
Tener localizado la creación del código.
Pueda extenderse para crear clases no creadas aún.
Ejemplo del Patrón
● Librería de clase log, para depuración.
● Distintos tipos de gestión de mensajes (en pantalla, en fichero log o mediante un evento a un
proceso).
● Por su diferencia implementación, se implementan en clases distintas.
Se puede trabajar con clase abstracta.
Se escriben los mensajes sin saber la recepción.
¿Cómo se crea el log concreto?
Si cada componente lo crea no se puede gestionar.
No se podrían emplear clases Log diseñadas después.
Idea: Delegar la creación a una clase abstracta.
Ejemplo de uso
void iniciaAplicación(LogFactory *factory) {
Log *log = factory.creaLog('MAIN');
log>
escribeMsgDepuración(“Iniciando sistema”);
SistemaAutentificacion autentifica(factory>
creaLog('AUTENTIFICACION') );
ServidorPeticiones peticiones (autentifica, factory>
creaLog('SERVIDOR') );
log>
escribeMsgDepuración(“Empiezo a atender peticiones”);
peticiones.atiendePeticiones();
}
Participantes:
Factory o Creator: Define el método de creación.
Abstract Creator: Se define por cada clase a crear.
Ventajas:
Permite extender el modelo con herencia (ej: GZipLog)
No se especifican clases concretas en el código.
Permite generar jerarquías paralelas
Inconvenientes:
Mayor complejidad: Aumenta el número de clases
Patrones relacionados
Singleton, puede usarse para crear sólo un objeto
Factoría (peligro en su uso).
Ofrece una misma solución al problema que el patrón Prototipo
Builder, Cuando la complejidad de creación aumenta
Variante del Patrón: Parametrizado
Hace uso de switch, no de herencia.
Encapsula, pero no permite extender.
getLog(string name) {
Properties config(“config”);
String type = config.getProperties(name +”.type”);
if (type.equals(“file”)) {
return new FicheroLog(name);
}
else if (type,equals(“console”)) {
return new ConsolaLog(name);
}
...
Decorator
Nombre del patrón: Decorator (Decorador)‫‏‬
Clasificación del patrón: Estructural.
Problema
Este patrón nos permite asignar dinámicamente propiedades y comportamientos a
objetos individuales y no a toda una clase
Contexto
Añadir propiedades y/o funcionalidades a objetos de una interfaz de usuario.
Prioridades:
El Decorator se puede aplicar de forma recursiva para añadirle nuevas
responsabilidades permitiendo una mayor flexibilidad de la funcionalidad del objeto.
Objetivo:
Añadir objetos individuales de forma dinámica y transparente para no afectar a otros
objetos.
Las funcionalidades no tienen por que ser aplicadas.
Cuando no se puede usar herencia por tener una gran número de ellas para cubrir
todas las combinaciones.
Estructura:
Participantes:
Componente: Interfaz para objetos donde añadir funcionalidad dinámicamente.
Operacion() : Método abstracto heredado por la clase ComponenteConcreto.
ComponenteConcreto: Objeto al que añadir nuevas funcionalidades mediante la clase
Decorator.
Operacion() : Método que le da una funcionalidad a la clase.
Decorador: Interfaz que se ajusta al interfaz componente.
Operacion() : Método abstracto heredado de la clase Componente.
DecoradorConcretoA: Añade funcionalidades al componente.
Operacion() : Método que llama a las funcionalidades de la clase.
ComportamientoAñadidoA() : Método que le da una funcionalidad dinámica a
la clase.
AtributoA: Atributo usado por el método ComportamientoAñadidoA.
Ventajas:
Flexibilidad en comparación con la herencia estática.
Evita sobrecargar de funcionalidad las clases padres de la jerarquía.
Inconvenientes:
Muchos objetos pequeños que complican su aprendizaje y depuración
El decorador y sus componentes no son idénticos. No se puede confiar en que ambos
sean iguales.
Patrones relacionados
Adapter: Proporciona una interfaz nueva mientras que el Decorator solo le da
funcionalidades nuevas.
Composite: Proporciona objetos nuevos mientras que el Decorator solo añade
funcionalidades.
Strategy: Cambia la estructura interna de un objeto mientras que el Decorator
modifica su exterior.
Ejemplo
Iterator
Nombre del patrón: Iterator (Iterador)‫‏‬
Clasificación del patrón: Comportamiento.
Problema:
Nos permite acceder secuencialmente a los elementos de un objeto agregado sin modificar la
funcionalidad interna.
Contexto:
El Iterator es una interfaz que tiene la funcionalidad de acceder y recorrer cada uno de los
elementos del objeto agregado.
Objetivo:
Proporciona una interfaz uniforme para recorrer distintas estructuras agregadas
(iteración polimórficas).
Permite realizar diferentes recorridos sobre objetos agregados
Separa el mecanismo de recorrido del objeto “Lista” permitiendo iteradores con
diferentes políticas de recorrido sin necesidad de enumerarlos en la interfaz de “Lista”
Estructura:
Participantes:
IteradorConcreto: Implementa la interfaz Iterator y mantiene la posición actual en el
recorrido del agregado.
Iterador: Interfaz para recorrer los elementos y acceder a ellos.
Primero(): Accede al primer elemento.
Siguiente(): Accede al próximo elemento.
HaTerminado(): Comprueba el fin del agregado.
ElementoActual(): Hacer referencia al elemento actual.
Agregado: Define una interfaz para crear un objeto iterador.
CrearIterador() : Método abstracto.
AgregadoConcreto: Implementación del interfaz de creación de Iterator.
CrearIterador() : retorna una instancia del iterador.
Ventajas:
Soporte a variaciones en el recorrido de un agregado. Los iteradores hacen fácil
cambiar el algoritmo de recorrido, con sólo reemplazar la instancia del iterador a una
diferente.
Los iteradores simplifican la interfaz de los agregados, ya que la interfaz de los
recorridos se encuentra en los iteradores y no en los agregados.
Más de un recorrido puede estar pendiente en un agregado, puesto que cada iterador
mantiene la pista de su recorrido.
Inconvenientes:
Puede existir una interferencia por parte de las actualizaciones durante un recorrido
secuencial.
Patrones relacionados
Composite: Se suele usar en estructuras recursivas.
Factory: Los iteradores polimórficos se basan en este método.
Patrones Colaboradores:
Mementos: Se usa para representar el estado de una iteración.
Interface Lista
template <class Elemento>
class Lista {
public:
Lista(long tamanio = MAX_LISTA);
long Contar() const;
Elemento& Obtener(long indice) const;
…
};
Implementación
lista<Animal> animales;
Animal animal;
…
lista<Animal>::Iterador it;
cout << “Animales: “;
it = animales.Primero();
while (! animales.HaTerminado()) {
animal = *it;
cout << “,” << animal;
animales.Siguiente();
}
cout << endl;
Interface Iterador
template <class Elemento>
class Iterador {
public:
virtual void Primero();
virtual void Siguiente();
virtual bool HaTerminado() cosnt = 0;
virtual Elemento ElementoActual() cosnt = 0;
protected:
Iterador();
};
Strategy
Nombre del patrón: Strategy (Estrategia)‫‏‬
Clasificación del patrón: Comportamiento
Problema: Existen diversas estrategias para abordar un problema.
Contexto: Se tiene un problema que puede ser implementado o afrontado de distintas formas
y cuyo interfaz esté bien definido y es común para dichas formas, pudiendo ser cualquiera de
ellas válida o más deseable en determinadas situaciones y permitiendo el cambio entre las
distintas estrategias en tiempo de ejecución.
Prioridades:
Reducir complejidad sistema (número de clases).
Eficiencia (elección de la mejor estrategia icluso en tiempo de ejecución)
Flexibilidad (si se necesitara una nueva estrategía tan solo habría que implementar la
interfaz adecuadamente).
Objetivo:
Encapsular funcionalidad en forma de objetos, los cuales puedan ser modificados de
forma dinámica por los clientes.
Estructura:
Participantes:
Contexto (Context):
Clase que controla el acceso a las diferentes estragias.
Estrategia (Strategy):
Clase que define la interfaz común a todas las estrategias.
EstrategiaConcretaX (ConcreteStrategyX):
Clase que implementa una de las posibles estrategias.
Ventajas:
Facilidad de agregar una nueva opción dinámicamente.
Encapsulamiento de las estrategías. Se pueden añadir nuevas implementaciones y
quitar o modificar las existentes de una forma muy limpia y eficiente.
Reducción número de clases. Una única clase utiliza diferentes estrategias
(comportamientos) .
Inconvenientes:
Incremento del número de objetos.
Sobrecarga de la comunicación entre la clase Contexto y Strategy sin importar la
complejidad de la estrategia.
No es deseable que el cliente decida que estrategia seguir.
Patrones relacionados
Bridge: Tiene el mismo diagrama UML, pero se diferencia en sus “intenciones”:
Strategy, indica el comportamiento; Bridge, la estructura.
Ejemplo: Ordenación
Strategy (SortStrategy)
Define un interfaz común para todos los algoritmos soportados. Context utiliza esta
interfaz para utilizar algún algoritmo definido por un ConcreteStrategy
ConcreteStrategy (QuickSort, ShellSort, MergeSort)
Implementa el algoritmo utilizando la interfaz Strategy.
Context (SortedList)
Está configurado con un objeto de ConcreteStrategy.
Mantiene una referencia a un objeto Strategy.
Puede definir un interfaz para permitir que Strategy acceda a sus datos.
namespace StrategyPatterns
{
// Definición del interfaz
public interface ISort
{
void Sort(List<string> list)
} // Implementación QuickSort
public class CQuickSorter : ISort
{
void Sort(List<string> list)
{
// Implementación Quick Sort
}
}
// Implementación Burbuja
public class CBurbujaSorter
{
void Sort(List<string> list)
{
// Implementación en Burbuja
}
}
// Implementación MergeSort
public class CMergeSort
{
void Sort(List<string> list)
{
// Implementación MergeSort
}
}
public class Contexto
{
private ISort sorter;
public Contexto(ISort sorter)
{
this.sorter = sorter;
}
public ISort Sorter
{
get{return sorter;)
}
}
public class MainClass
{
static void Main()
{
List<string> myList = new List<string>();
myList.Add("Hola mundo");
myList.Add("Otro item");
myList.Add("Tercer item");
Contexto cn = new Contexto(new CQuickSorter());
cn.Sorter.Sort(myList); // Ordenado con quicksort
myList.Add("Uno más para el merge sort");
cn = new Contexto(new CMergeSort());
cn.Sorter.Sort(myList); // Ordenado con mergesort
}
}
}
Ejemplo II: Coche
Ejemplos reales
1. En .NET se aplica con bastante frecuencia. Uno de los ejemplos más concretos se corresponde
con la infraestructura de acceso a base de datos en la cual existen una serie de interfaces
(IDbConnection, IDBCommand...) que reciben una implementación concreta según la base de
datos (estrategia) que se desee implementar (OracleConnection, OracleCommand...)
2. En los lectores de datos. El interfaz vuelve a estar bien definido, necesitamos un determinado
flujo de datos (un stream de bytes, una cadena de texto claro, una cadena xml) pero el origen
de ese determinado flujo puede variar. De esta forma existen diversos interfaces
(IStreamReader, IXMLReader, ITextReader) así como una serie de implementadores de dichos
interfaces, TCPReader, FileReader... etc.
Visitor
Nombre del patrón: Visitor - Visitante
Clasificación del patrón: Patrones de comportamiento
Problema: Queremos añadir un nuevo método a la jerarquía de clases, pero el hecho de hacerlo
estropeará el diseño.
Contexto: Implementación del sistema separando las funcionalidades heterogéneas de las clases,
sin modificar la jerarquía de clases.
Prioridades: Flexibilidad, para que la adición de nuevos elementos alteren el mínimo número de
clases posible.
Objetivos: Encapsular la operación que se realizará sobre los elementos de una estructura de
datos. De esta forma podemos realizar cambios posteriores en la operación sin cambiar las clases
de elementos sobre los que opera.
Estructura
Participantes:
Visitante:
Declara la interfaz de visitante con la operación Visitar para cada clase de ElementoConcreto en la
estructura de objetos.
El nombre de operación y su signatura identifican la clase que envía la petición Visitar al Visitante,
permitiendo al Visitante determinar la clase concreta del elemento a visitar.
El Visitante puede acceder al elemento directamente a través de su interfaz particular.
VisitanteConcreto:
Implementa cada operación declarada por el Visitante.
Proporciona el contexto para el algoritmo y almacena el estado local (normalmente acumula los
resultados durante el paso por la estructura).
Cliente:
Crea un objeto VisitanteConcreto y recorre la estructura de objetos, visitando cada elemento con
el visitante.
Elemento:
Define la operación Acceptar que tiene un visitante como argumento.
Elemento Concreto:
Implementa la operación Aceptar.
Estructura de Objetos:
Enumeración de sus elementos.
Proporciona una interfaz de alto nivel para que los visitantes visiten sus elementos.
Pude ser una composición o colección (list, set).
Colaboraciones:
El cliente crea un objeto VisitanteConcreto y recorre la estructura de objetos, visitando cada
elemento con el visitante.
Cuando un elemento es visitado, hace la llamada al método del visitante que corresponde con su
clase.
El elemento se proporciona a si mismo como argumento para esta operación para dejar que el
visitante acceda a su estado, si es necesario.
Ventajas:
Se pueden añadir nuevas operaciones es fácilmente.
Se pueden agrupar operaciones relacionadas en una clase en lugar de modificar o derivar clases
para ello.
Colaborar con el patron Composite.
Inconvenientes:
Dificulta la adición de nodos (requiere la modificación de cada visitante existente), por lo que el
conjunto de clases tiene que estar definido a priori.
Cada clase tiene que tener un método de aceptación (accept method).
Patrones relacionados:
Composite: para aplicar una operación a una estructura de objetos.
Interpreter: se puede aplicar el patrón visitante para la interpretación.
Ejemplo:
Consideremos este ejemplo de la interfaz de un modem.
Contiene los métodos genéricos de los distintos modelos disponibles.
Supongamos que queremos configurar los modems para Linux con un método nuevo.