sintesis

¿Por qué?

  • Inefectividad, ineficacia y/o ineficiencia, del Proyecto Software

    • porque tiene malas variables en su economía:

      • ámbito incumplido y/o

      • tiempo incumplido y/o

      • coste incumplido;

    • porque tiene mala calidad del software, fiabilidad, usabilidad, interoperatividad, seguirdad, …​ (-ilities)

    • porque tiene mala mantenibilidad

      • viscoso, porque no se puede entender con facilidad y/o

      • rígido, porque no se puede cambiar con facilidad y/o

      • frágil, porque no se puede probar con facilidad y/o

      • inmovil, porque no se puede reutilizar con facilidad y/o

    • porque tiene complejidad arbitraria

davidProyectoImplantacionDiseñoMal

arbolNoMantenible

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

  • Los expertos, en diseño orientados a objetos o cualquier ámbito/disciplina/…​, comparten/sugieren/…​ Conocimiento/Know How:

  • Obtener un diseño reusable y flexible correctamente a la primera es difícil, sino no imposible

  • No resolver cada problema desde cero

  • Intentar reusar una buena solución varias veces modificándola cada vez

¿Cuándo? ¿Dónde? ¿Quién?

  • En el área de la arquitectura de edificios y ciudades:

    • 1977. Christopher Alexander publica A Pattern Language propone un primer catálogo de patrones

    • 1979. Christopher Alexander publica The Timeless Way of Building propone el concepto de patrón

  • En el área del desarrollo de software orientado a objetos (comunidad de Smalltalk)

    • 1987. Ward Cunningham y Kent Beck publican _Using Pattern Languages for OO Programs_ en OOPSLA

    • 1990. Erich Gamma, Richard Helm, Ralph Johnson y John Vlissides (Gang of Four -GoF-, La Banda de los Cuatro) publican Design Patterns con un catálogo de 23 patrones de diseño

problemasRecurrentes2

¿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é?

  • Efectividad, eficacia y eficiencia, del Proyecto Software

    • porque tiene buenas variables en su economía:

      • ámbito cumplido y

      • tiempo cumplido y

      • coste cumplido;

    • porque tiene buena calidad del software, fiabilidad, usabilidad, interoperatividad, seguirdad, …​ (-ilities)

    • porque tiene buena mantenibilidad

      • fluido, porque se puede entender con facilidad y/o

      • flexible, porque se puede cambiar con facilidad y/o

      • fuerte, porque se puede probar con facilidad y/o

      • reusable, porque se puede reutilizar con facilidad y/o

    • porque tiene complejidad inherente

davidProyectoImplantacionDiseño

arbolMantenible

visionGeneral
  • Registrar la experiencia en diseño de software orientado a objetos como patrones de diseño de forma que la gente pueda reusarlo efectivamente.

    • Expresar técnicas probadas más accesibles a los desarrolladores de nuevos sistemas.

    • Ayudar a elegir entre las alternativas de diseño que hacen un sistema reutilizable y a evitar alternativas que comprometen la reutilización.

    • Conseguir un diseño correcto más rápido.

  • Nombrar, explicar y evalúar sistemáticamente diseños importantes y recurrentes en un sistema orientado a objetos.

    • Mejorar la documentación y mantenimiento de los sistemas existentes mediante el suministro de una especificación explícita de las interacciones de clases y objetos y su intención subyacente.

¿Cómo?

Elementos

Elementos Esenciales

  • Para la descripción general del patrón se proponen 13 elementos agrupados en 4 elementos esenciales:

    • Nombre: Nombre y Sinónimo

    • Problema: Motivación, Intención y Aplicabilidad

    • Solución: Estructura, Participantes, Colaboraciones, Implementación, Códigos de Ejemplo, Usos Conocidos y Patrones Relacionados

    • Consecuencias: Consecuencias

partesPatron

Nombre

partesNombre
Elemento Descripción Ejemplo

Nombre

  • Una o dos palabras que transmiten la esencia del patrón (problema, solución y consecuencias) permite:

    • Aumentar inmediatamente nuestro vocabulario de diseño, para comunicarnos con otros colegas o nosotros mismos y en la propia documentación.

    • Diseñar a un nivel más alto de abstracción.

    • Pensar en diseños y comunicarlos con sus compromisos.

Singleton

Sinónimos

Otros nombres conocidos para el patrón, si los hubiere.

-

Problema

partesProblema
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

  • Para algunas clases es importante compartir una única instancia en el sistema. Ejemplos:

    • aunque haya muchas impresoras en un sistema, debería haber una única cola de impresión

    • debería haber un solo sistema de ficheros y un solo gestor de ventanas

    • un filtro digital tendría un único convertidor analógico/digital

    • un sistema de cuentas bancarias será dedicado para servir a una compañía

    • Código global, sin objeto!!!

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

  • Debe ser exactamente una única instancia de la clase y debe ser accesible a los clientes desde un punto de acceso bien conocido.

  • Cuando la única instancia debería ser extensible por sub-clasificación y los clientes deberían ser capaces de usar la instancia extendida sin modificar su código

Solución

partesSolucion
Elemento Descripción Ejemplo: Singleton

Estructura

Diagrama de clases del patrón

singleton

Participantes

Reparto de responsabilidades entre las clases del patrón

  • Singleton define una operación Instance que permite a los clientes acceder a su única instancia.

    • Instance es una operación de clase (miembro estático)

    • Singleton puede ser responsable de crear su propia instancia única

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

Garantizar una única instancia. Código
Garantizar varias instancias. Código
Garantizar subclasificación. Código

Código de ejemplo

Ejemplo de código que ilustra una implementación del patrón en diversos lenguajes.

class Printer {

    private static Printer instance = null;

    public static getInstance() {
        if (Printer.instance == null){
            Printer.instance = new Singleton();
        }
        return Printer.instance;
    }

    private attribute;

    private Printer(){
        // init
    }

    public method(){
        // body
    }
}

Usos conocidos

Dos o más ejemplos de diferentes aplicaciones en sistemas reales

  • ChangeSet int Smalltalk-80

  • Session and WidgetKit en InterViews

Patrones relacionados

Patrones de diseño estrechamente relacionados por diferencias o posible colaboración

  • Abstract Factory

  • Builder

  • Prototype

Consecuencias

partesConsecuencia
Elemento Descripción Ejemplo: Singleton

Consecuencia

  • Ventajas y desventajas, compromisos de espacio/tiempo, cuestiones del lenguaje (estático) y de ejecución (dinámico).

    • Impacto en la reutilización, flexibilidad, extensibilidad o portabilidad de un sistema, indicando qué parte permite variar de forma independiente

    • Evaluación de alternativas de diseño con la comprensión de los costes y beneficios de aplicar el patrón.

  • Acceso controlado a una única instancia. Como la clase encapsula su única instancia, puede tener un control estricto sobre cómo y cuándo los clientes acceden a ésta.

  • Reducir el espacio de nombres. Es una mejora sobre las variables globales ya que se evita contaminar el espacio de nombres con las variables globales que almacenen las instancias únicas.

  • Permite el refinamiento de operaciones y su representación. Se pueden tener subclases y es fácil configurar una aplicación con una instancia de esta clase extendida que se necesite en tiempo de ejecución.

  • Permite un número variable de instancias. Facilita cambiar de opinión y permitir más de una instancia de la clase. Además, se puede utilizar el mismo enfoque para controlar el número de instancias que utiliza la aplicación.

  • Más flexible que operaciones de clase (static). Esta técnica hace que sea difícil cambiar un diseño para permitir más de una instancia de una clase. Y las subclases no pueden redefinirlas polimórficamente

Clasificación

relacionPatron

Patrones Creacionales

PatronCreacional

abstractFactory

builder

factoryMethod

prototype

singleton

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.

patronCreacionalAbstracto
  • Menor tamaño, abstrayéndose del proceso de creación de instancias

  • Alta cohesión, enfocándose en la gestión de los productos.

  • Bajo acoplamiento, conociendo únicamente las interfaces de los objetos a través de sus clases abstractas, sin los constructores específicos

  • Menor complejidad, con menor tamaño, alta cohesión y bajo acoplamiento anteriores.

  • Principio Abierto/Cerrado, con la flexibilidad, extensibilidad, …​ para configuraciones de estruturas y funcionalidades futuras. La configuración que puede ser estática (tiempo de compilación) o dinámica (tiempo de ejecución), añadiendo un objeto o una clase.

Abstract Factory

Problema

Motivación
motivacionAbstractFactory

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.

  • Cohesión

    • La cohesión de la clase Surgeon no es adecuada puesto que asume varias responsabilidades dispares o que no le atañen, como la obtención del instrumental dependiendo de la localización (creación de objetos).

  • Acoplamiento

    • El acoplamiento de la clase Surgeon (ej. 4) depende del número de localizaciones y del número de familias de productos.

      • Se calcula como el producto del número de localizaciones (ej. selva y quirófano, 2) por el número de familias de productos (ej. CuttingTool y DryingTool, 2), con la simplificación de que no se comparten instrumentos entre distintas localizaciones.

      • Su resultado es de orden cuadrático, O(n2).

AcoplamientoWrongAbstractFactory

AcoplamientoWrongAbstractFactory2

  • Granularidad

    • La granularidad de la clase Surgeon (ej. 5) depende de la cantidad de métodos requeridos.

      • Se calcula como la suma de los métodos relativos a la responsabilidad de la clase (ej. operate, 1) y el producto del número de localizaciones por el número de familias de productos (ej. selva y quirófano, 2, por CuttingTool y DryingTool, 2).

      • Su resultado es de orden cuadrático, O(n2).

GranularidadWrongAbstractFactory

GranularidadWrongAbstractFactory2

Intención
intencionAbstractFactory

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

  • La cohesión de la clase Surgeon es adecuada puesto que asume una única responsabilidad, centrada en la operación, colaborando con el servicio del instrumentalista y los instrumentos suministrados.

Acoplamiento

  • El acoplamiento de la clase Surgeon (ej. 3) depende del número de familias de productos.

    • Se calcula como la suma del instrumentalista (ej. Instrumentalist, 1) más el número de familias de productos (ej. CuttingTool y DryingTool, 2).

    • Su resultado es de orden lineal, O(n).

AcoplamientoFineAbstractFactory

AcoplamientoFine2AbstractFactory

Granularidad

  • La granularidad de la clase Surgeon (ej. 1) depende de la cantidad de métodos requeridos.

    • Se calcula como la suma de los métodos relativos a la responsabilidad de la clase (ej. operate, 1).

    • Su resultado es de orden constante, O(1)

GranularidadFineAbstractFactory

GranularidadFineAbstractFactory2

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

  • Un sistema debe ser independiente de cómo se crean, componen y representan sus productos.

  • Un sistema debe ser configurado con una familia de productos de entre varias. Una familia de objetos producto relacionados está diseñada para ser usada conjuntamente, y es necesario hacer cumplir esta restricción.

  • Se quiere proporcionar una biblioteca de clases de productos, y sólo se desea revelar sus interfaces, no sus implementaciones.

Solución

Estructura General

estructuraGeneralAbstractFactory

Participantes

  • AbstractFactory: declara una clase abstracta o interfaz que define los métodos de creación de los distintos tipos de AbstractProduct.

    • Tiene una operación que devuelve un nuevo objeto para cada clase abstracta de producto. Los tipos de producto está codificados en las signaturas de las operaciones. Añadir un nuevo tipo de producto requiere cambiar la interfaz de AbstractFactory y todas las clases que dependen de ella.

    • Los clientes llaman a estas operaciones para obtener instancias de productos, pero no son conscientes de las clases concretas que están usando. En otras palabras, los clientes no tienen que atarse a una clase concreta, sino sólo a una interfaz definida por una clase abstracta.

  • AbstractProduct: declara una clase abstracta o interfaz para un tipo de objeto producto.

  • ConcreteFactory: clase derivada de AbstractFactory que implementa los métodos para crear objetos producto concretos.

    • El modo más común de hacer esto es definiendo un método de fabricación para cada producto.

  • ConcreteProduct: clase derivada del AbstractProduct que representa un producto concreto para un estándar concreto de interfaz de usuario.

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
  • El patrón Abstract Factory ayuda a controlar las clases de objetos que crea una aplicación. Como una AbstractFactory encapsula la responsabilidad y el proceso de creación de objetos producto, aísla a los clientes de las clases de implementación. Los clientes manipulan las instancias a través de sus interfaces abstractas. Los nombres de las clases producto quedan aisladas en la implementación de la ConcreteFactory; no aparecen en el código cliente.

  • La clase ConcreteFactory sólo aparece una vez en una aplicación, cuando se crea. Esto facilita cambiar la ConcreteFactory que usa una aplicación. Como una AbstractFactory crea una familia completa de productos, toda la familia de productos cambia de una vez.

  • Cuando se diseñan objetos producto en una familia para trabajar juntos, es importante que una aplicación use objetos de una sola familia a la vez. El patrón Abstract Factory facilita que se cumpla esta restricción.

  • Ampliar la AbstractFactory para producir nuevos tipos de productos no es fácil. Esto se debe a que la interfaz AbstractFactory fija el conjunto de productos que se pueden crear.

    • Permitir nuevos tipos de productos requiere ampliar la interfaz, lo que a su vez implica cambiar la clase AbstractFactory y todas sus subclases.

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
motivacionBuilder
  • Una persona se responsabiliza de una dieta variada de menús, básico o gourmet o …, todos ellos compuestos por diferentes platos: entrante, ensalada u ostras o …; plato principal, pollo o solomillo o …; postre, natillas o soufflé o …

  • Esto complica la comprensión de su comportamiento y tiende a crecer a medida que surgen nuevos platos.

  • Cohesión

    • La cohesión de la clase Person no es adecuada puesto que asume varias responsabilidades dispares o que no le atañen, como la preparación de diferentes platos dependiendo del tipo de menú de su dieta (creación de objetos).

  • Acoplamiento

    • El acoplamiento de la clase Person (ej. 6) depende del número de tipos de menú y del número de platos del menú.

    • Se calcula como el producto del número de tipos de menú (ej. básico y gourmet, 2) por el número de platos del menú (ej. entrante, plato principal y postre, 3), con la simplificación de que no se comparten platos en diferentes menús.

    • Su resultado es de orden cuadrático, O(n2).

AcoplamientoWrongBuilder

AcoplamientoWrongBuilder2

  • Granularidad

    • La granularidad de la clase Person (ej. 7) depende de la cantidad de métodos requeridos.

    • Se calcula como la suma de los métodos relativos a la responsabilidad de la clase (ej. eat, 1) y el producto del número de tipos de menús por el número de platos del menú (ej. básico y gourmet, 2, por entrante, plato principal y postre, 3).

    • Su resultado es de orden cuadrático, O(n2).

GranularidadWrongBuilder

GranularidadWrongBuilder2

Intención
  • Si la responsabilidad de la persona incluye únicamente la solicitud del menú (básico, gourmet, …) y otros, cocineros, se responsabilizan por separado de cómo se preparan los platos del menú dependiendo del tipo de menú, ninguna clase será compleja y se evitará la tendencia a crecer.

intencionBuilder
  • Cohesión

    • La cohesión de la clase Person es adecuada puesto que asume una única responsabilidad, centrada en comer, colaborando con el servicio del cocinero que suministra los platos.

  • Acoplamiento

    • El acoplamiento de la clase Person (ej. 2) depende del tipo del cocinero (ej. Chef, 1) y del tipo del plato del menú (ej. Dish, 1).

    • Su resultado es de orden constante, O(1).

AcoplamientoFineBuilder

AcoplamientoFineBuilder2

  • Granularidad

    • La granularidad de la clase Person (ej. 1) depende de la cantidad de métodos requeridos.

    • Se calcula como la suma de los métodos relativos a la responsabilidad de la clase (ej. eat, 1).

    • Su resultado es de orden constante, O(1).

GranularidadFineBuilder

GranularidadFineBuilder2

Intención Aplicabilidad
  • La intención de este patrón es separar la construcción de un objeto complejo de su representación, de tal forma que el mismo proceso de construcción puede crear diferentes representaciones.

  • El algoritmo para la creación de un objeto complejo debe ser independiente de las partes que componen el objeto y la forma en que están montadas.

  • El proceso de construcción debe permitir diferentes representaciones para el objeto que se construye.

Solución

Estructura General

estructuraGeneralBuilder

Participantes

  • Director: clase que solicita la creación de un objeto utilizando la interfaz proporcionada por el AbstractBuilder.

  • AbstractBuilder: declara una clase abstracta o interfaz que define una operación para cada componente que un Director puede pedirle crear.

    • Las operaciones no hacen nada por defecto.

    • Los constructores construyen sus productos paso a paso. Por tanto, la interfaz de la clase AbstractBuilder debe ser lo suficientemente general como para permitir construir productos por parte de todos los tipos de ConcreteBuilder.

  • ConcreteBuilder: clase derivada de AbstractBuilder que redefine las operaciones de construcción de las piezas en las que está interesado para la creación del producto final. Se encarga también de ensamblar dichas piezas para componer el ConcreteProduct y devolver una instancia de este.

    • A veces, podríamos necesitar acceder a las partes del producto que ya fueron construidas. Con las estructuras arbóreas que se crean de abajo a arriba, el ConcreteBuilder devolvería nodos hijos al Director, el cual los devolvería al constructor para construir los nodos padre.

  • AbstractProduct: declara una clase abstracta o interfaz para un tipo de objeto producto.

  • ConcreteProduct: clase derivada del AbstractProduct que representa un producto concreto.

    • 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.

    • Normalmente, como el cliente suele configurar al Director con el ConcreteBuilder adecuado, sabe qué subclase concreta de Builder se está usando y puede manejar sus productos en consecuencia.

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
  • Se proporciona al Director una interfaz abstracta para la construcción del producto. La interfaz permite al ConcreteBuilder ocultar la representación y la estructura interna del producto. También oculta cómo el producto se ensambla. Debido a que el producto se construye a través de una interfaz abstracta, todo lo que tiene que hacer para cambiar la representación interna del producto es definir un nuevo tipo de ConcreteBuilder.

  • Mejora la modularidad encapsulando la forma en que un objeto complejo es construido y representado. Cada ConcreteBuilder contiene toda la funcionalidad necesaria para crear y ensamblar un tipo concreto de producto. Los clientes no necesitan saber nada acerca de las clases que definen la estructura interna del producto; estas clases no aparecen en la interfaz del Builder.

  • Construye el producto paso a paso bajo el control del Director. Sólo cuando el producto está terminado, el Director lo recupera desde el ConcreteBuilder. Por lo tanto, la interfaz del ConcreteBuilder refleja el proceso de construcción del producto, pudiendo coordinar y validar la creación de todos los componentes. Esto le da un mayor control sobre el proceso de construcción y, por lo tanto, de la estructura interna del producto resultante.

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
  • Un repartidor se responsabiliza del reparto de paquetes con el mismo algoritmo, recoger paquete, localización del destino, seleccionar vehículo disponible, conducir, entregar paquete, … pero dependiendo de los tipos de vehículo disponibles, lo realizará con un vehículo u otro, bicicleta o moto o …

    • Esto complica la comprensión de su comportamiento y tiende a crecer a medida que surgen nuevos vehículos.

motivacionFactoryMethod
  • Cohesión

    • La cohesión de la clase DeliveryMan no es adecuada puesto que asume varias responsabilidades dispares o que no le atañen, como la obtención del vehículo dependiendo de los tipos de vehículos disponibles (creación de objetos).

  • Acoplamiento

    • El acoplamiento de la clase DeliveryMan (ej. 3) depende del número de vehículos.

    • Se calcula como la suma del tipo de vehículo (ej. Vehicle, 1) más el número de vehículos (ej. Bicycle y Motorcycle, 2).

    • Su resultado es de orden lineal, O(n).

AcoplamientoWrongFactoryMethod

AcoplamientoWrongFactoryMethod2

  • Granularidad

    • La granularidad de la clase DeliveryMan (ej. 3) depende de la cantidad de métodos requeridos.

    • Se calcula como la suma de los métodos relativos a la responsabilidad de la clase (ej. deliver, 1) y el número de vehículos (ej. Bicycle y Motorcycle, 2).

    • Su resultado es de orden lineal, O(n).

GranularidadWrongFactoryMethod

GranularidadWrongFactoryMethod2

Intención
itencionFactoryMethod
  • Si la responsabilidad de repartidor incluye únicamente la solicitud de un vehículo abstracto y otros, repartidores concretos, se responsabilizan por separado de cómo se obtiene el vehículo concreto dependiendo de los vehículos disponibles, ninguna clase será compleja y se evitará la tendencia a crecer.

  • Cohesión

    • La cohesión de la clase DeliveryMan es adecuada puesto que asume una única responsabilidad, centrada en el reparto, colaborando con el servicio del repartidor concreto y el vehículo suministrado.

  • Acoplamiento

    • El acoplamiento de la clase DeliveryMan (ej. 1) depende del tipo del vehículo (ej. Vehicle, 1).

    • Su resultado es de orden constante, O(1).

AcoplamientoFineFactoryMethod

AcoplamientoFineFactoryMethod2

  • Granularidad

    • La granularidad de la clase DeliveryMan (ej. 2) depende de la cantidad de métodos requeridos.

    • Se calcula como la suma de los métodos relativos a la responsabilidad de la clase (ej. deliver, 1) y el método para obtener el vehículo (ej. getVehicle).

    • Su resultado es de orden constante, O(1).

GranularidadFineFactoryMethod

GranularidadFineFactoryMethod2

Intención Aplicabilidad
  • La intención de este patrón es definir una interfaz para crear un objeto, pero delegar a las subclases qué clase concreta instanciar, permitiendo a una clase diferir la instanciación a las subclases.

  • Una clase no puede prever la clase de objetos que debe crear.

  • Una clase quiere que sean sus subclases quienes especifiquen los objetos que ésta crea.

  • Las clases delegan la responsabilidad en una de entre varias clases auxiliares y queremos localizar qué subclase de auxiliar concreta es en la que se delega.

Solución

Estructura General

estructuraGeneralFactoryMethod

Participantes

  • Creator: declara una clase abstracta o interfaz que define los métodos de fabricación (Factory Method) de los distintos tipos de ConcreteProduct.

    • Podría tratarse también de una clase concreta que proporcione una implementación predeterminada del método de fabricación. 

  • AbstractProduct: declara una clase abstracta o interfaz para un tipo de objeto producto.

  • ConcreteCreator: clase derivada de Creator que implementa el método de fabricación (Factory Method) para crear su correspondiente ConcreteProduct.

  • ConcreteProduct: clase derivada del AbstractProduct que representa un producto concreto para un estándar concreto de interfaz de usuario.

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
  • Los clientes pueden encontrar útiles los métodos de fabricación, especialmente en el caso de jerarquías de clases paralelas.

    • Las jerarquías de clases paralelas se producen cuando una clase delega alguna de sus responsabilidades a una clase separada (como las clases de estructuras de datos y la obtención de sus iteradores). Los métodos de fabricación definen la conexión entre las dos jerarquías de clases, localizando qué clases van juntas.

  • Un inconveniente potencial de los métodos de fabricación es que los clientes pueden tener que crea subclases de Creator sólo para fabricar un nuevo tipo de ConcreteProduct. La herencia está bien siempre y cuando el cliente tenga que crear las subclases de todos modos, ya que, si no, se estaría introduciendo una nueva vía de futuros cambios.

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
  • En un parque, un poeta se responsabiliza de vender poemas famosos que escribe en el momento en que se lo piden porque se los sabe de memoria. Pero si cambian los gustos, tendrá que adaptarse y aprender nuevos poemas.

  • Esto complica la comprensión de su comportamiento y tiende a crecer a medida que surgen nuevos poemas.

motivacionPrototype
  • Cohesión

    • La cohesión de la clase Poet no es adecuada puesto que asume varias responsabilidades dispares o que no le atañen, como la venta y la creación del poema concreto que ha sido solicitado (creación de objetos).

  • Acoplamiento

    • El acoplamiento de la clase Poet (ej. 3) depende del número de poemas.

    • Se calcula como la suma del tipo de poema (ej. Poem, 1) más el número de poemas (ej. LaCasadaInfiel y PoemaXX, 2).

    • Su resultado es de orden lineal, O(n).

AcoplamientoWrongPrototype

AcoplamientoWrongPrototype2

  • Granularidad

    • La granularidad de la clase Poet (ej. 3) depende de la cantidad de métodos requeridos.

    • Se calcula como la suma de los métodos relativos a la responsabilidad de la clase (ej. sell, 1) y el número de poemas (ej. LaCasadaInfiel y PoemaXX, 2).

    • Su resultado es de orden lineal, O(n).

GranularidadWrongPrototype2

GranularidadWrongPrototype

Intención
  • Si la responsabilidad de poeta incluye únicamente la solicitud de una copia del poema solicitado y otros, poemas concretos, se responsabilizan por separado de cómo se obtiene la copia del poema, ninguna clase será compleja y se evitará la tendencia a crecer.

itencionPrototype
  • Cohesión

    • La cohesión de la clase Poet es adecuada puesto que asume una única responsabilidad, centrada en la venta sin saber nada de poesía; se ha convertido en algo que parece un poeta, pero es un mero suministrador de poesías.

  • Acoplamiento

    • El acoplamiento de la clase Poet (ej. 1) depende del tipo del poema (ej. Poem, 1).

    • Su resultado es de orden constante, O(1).

AcoplamientoFinePrototype

AcoplamientoFinePrototype2

  • Granularidad

    • La granularidad de la clase Poet (ej. 1) depende de la cantidad de métodos requeridos.

    • Se calcula como la suma de los métodos relativos a la responsabilidad de la clase (ej. sell, 1.

    • Su resultado es de orden constante, O(1).

GranularidadFinePrototype

GranularidadFinePrototype2

Intención Aplicabilidad
  • La intención de este patrón es especificar las clases de objetos a crear usando una instancia prototípica y crear nuevos objetos por copias de este prototipo.

  • Un sistema deba ser independiente de cómo se crean, se componen y se representan sus productos y se cumpla alguna de las siguientes condiciones:

    • Las clases a instanciar sean especificadas en tiempo de ejecución (por ejemplo, mediante carga dinámica).

    • Se quiera evitar construir una jerarquía de clases fábrica paralela a la jerarquía de clases de los productos.

    • Las instancias de una clase puedan tener uno de entre sólo unos pocos estados diferentes. En esta situación puede ser más adecuado tener un número equivalente de prototipos y clonarlos, en vez de crear manualmente instancias de la clase cada vez con el estado apropiado.

Solución

Estructura General

estructuraGeneralPrototype

Participantes

  • Prototype: declara una clase abstracta o interfaz para clonarse.

  • ConcretePrototype: clase derivada de Prototype que implementa el método de clonación. El método de clonación devuelve una instancia de la misma clase con los mismos valores que la instancia Prototype original. La nueva instancia puede ser una copia profunda o superficial del original.

  • Director: clase que solicita la creación de un objeto utilizando la interfaz de clonación proporcionada por Prototype.

Implementación

Variaciones
  • Cuando el número de prototipos de un sistema no es fijo, y éstos pueden crearse y destruirse dinámicamente, se puede utilizar un registro de los prototipos disponibles. Los clientes no gestionarían ellos mismos los prototipos, sino que los guardarán y recuperarán del registro. Un cliente le pedirá al registro un prototipo antes de clonarlo.

    • Este gestor de prototipos es un almacén asociativo que devuelve el prototipo que concuerda con una determinada clave. Tiene operaciones para registrar un prototipo con una clave y para desregistrarlo. Los clientes pueden cambiar el registro o incluso navegar por él en tiempo de ejecución. Esto permite que los clientes extiendan el sistema y lleven un inventario del mismo sin necesidad de escribir código.

variacionPrototype
  • Cohesión

    • La cohesión de la clase Poet es adecuada puesto que asume una única responsabilidad, centrada en la venta, colaborando con el servicio de registro de poemas y la copia suministrada.

  • Acoplamiento

    • El acoplamiento de la clase Poet (ej. 1) depende del tipo del registro de poemas (ej. Poetry, 1).

    • Su resultado es de orden constante, O(1).

AcopñamientoFinePrototypePrototypeRegistry

AcopñamientoFinePrototypePrototypeRegistry2

  • Granularidad

    • La granularidad de la clase Poet (ej. 1) depende de la cantidad de métodos requeridos.

    • Se calcula como la suma de los métodos relativos a la responsabilidad de la clase (ej. sell, 1.

    • Su resultado es de orden constante, O(1).

GranularidadPrototypePrototypeRegistry

GranularidadPrototypePrototypeRegistry2

  • 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
  • Oculta al cliente las clases producto concretas, reduciendo así el número de nombres que conocen los clientes. Además, permite que un cliente use las clases específicas de la aplicación sin cambios.

  • Permite incorporar a un sistema una nueva clase concreta de producto simplemente registrando una instancia prototípica con el cliente. Esto es algo más flexible que otros patrones de creación, ya que un cliente puede instalar y eliminar prototipos en tiempo de ejecución.

  • Los sistemas altamente dinámicos permiten definir comportamiento nuevo mediante la composición de objetos – por ejemplo, especificando valores para las variables de un objeto – y no definiendo nuevas clases. Podemos definir nuevos tipos de objetos creando instancias de clases existentes y registrando esas instancias como prototipos de los objetos cliente. Un cliente puede exhibir comportamiento nuevo delegando responsabilidad en su prototipo.

    • Este tipo de diseño permite que los usuarios definan nuevas “clases” sin programación. De hecho, clonar un prototipo es parecido a crear una instancia de una clase. El patrón prototipo puede reducir en gran medida el número de clases necesarias en un sistema.

  • El patrón Prototype permite reducir la herencia. Los patrones Abstract Factory y Factory Method suele producir una jerarquía de clases AbstractFactory y Creator que es paralela a la jerarquía de clases de productos. El patrón Prototype permite clonar un prototipo en vez de decirle a un método de fabricación que cree un nuevo objeto. Por tanto, no es en absoluto necesaria una jerarquía de clases AbstractFactory o Creator. Este beneficio es aplicable principalmente a lenguajes como Java, C++, etc. que no tratan a las clases como objetos en toda regla.

  • Algunos entornos de tiempo de ejecución permiten cargar clases en una aplicación dinámicamente. El patrón Prototype es la clase para explotar dichas facilidades en un lenguaje como Java, C++, etc. Una aplicación que quiere crear instancias de una clase cargada dinámicamente no podrá hacer referencia al constructor de ésta estáticamente.

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
motivacionSingleton
  • Una escuela es una institución que consta de distintas entidades, dirección o departamentos o …, las cuales están constituidas por profesores. Además, se dispone de un guardia de seguridad cuyos servicios son requeridos por éstos. La ausencia de un mecanismo global para localizarle obliga a que se tenga que presentar al guardia a todas las clases organizativas para que éste llegue a ser conocido por los profesores y, en caso de necesitar ser sustituido, de avisarles de dicha sustitución para que puedan seguir utilizando sus servicios.

  • Esto tiende a crecer a medida que surgen nuevas clases organizativas a las que será necesario presentar al guardia de seguridad aunque no requieran sus servicios.

  • Cohesión

    • La cohesión de las clases organizativas (School, ManagementBoard y Department) no es adecuada puesto que asume varias responsabilidades dispares o que no le atañen, como el conocimiento del guardia de seguridad para que éste pueda ser presentado a los profesores.

  • Acoplamiento

    • El acoplamiento de las clases organizativas (School, ManagementBoard y Department) varía para cada tipo de clase organizativa. En el caso de la clase School, depende del número de entidades y del guardia de seguridad; y en el caso de las entidades ManagementBoard y Department, depende del tipo del profesor y del guardia de seguridad.

    • Al tratarse de una cantidad que varía para cada tipo de clase organizativa (ej. School, 3; ManagementBoard, 2; Department, 2), se considerará para el análisis la media de los acoplamientos de las clases que representan a los distintos tipos de clases organizativas (ej. 2,3).

    • Su resultado es de orden lineal, O(n).

AcoplamientoWrongSingleton

AcoplamientoWrongSingleton2

  • Granularidad

    • La granularidad de las clases organizativas (School, ManagementBoard y Department) depende de la cantidad de parámetros requeridos en su constructor y del parámetro correspondiente al guardia de seguridad. Dicha cantidad varía para cada tipo de participante (ej. School, 0 + 1 = 1; ManagementBoard, 0 + 1 = 1; Department, 0 + 1 = 1), por lo que se considerará para el análisis la media de todos los parámetros de los constructores de las clases que representan a los distintos tipos de clase organizativa (ej. 1).

    • Se calcula como la media de los parámetros de los constructores de las clases que representan a los distintos tipos de clase organizativa más el parámetro del guardia de seguridad; es decir, la suma de los parámetros de los constructores de todas las clases que representan un tipo de clase organizativa (ej. 1 + 1 + 1 = 3) dividido entre el número de tipos de clases organizativas (ej. 3).

    • Su resultado es de orden lineal, O(n).

GranularidadWrongSingleton

GranularidadWrongSingleton2

Intención
  • El uso de un servicio de guardia de seguridad global permite la flexibilidad de localizar e incluso cambiar de guardia sin afectar a nadie.

itencionSingleton
  • Cohesión

    • La cohesión de la clase School es adecuada puesto que asume una única responsabilidad, centrada en el arranque del curso académico, ignorando la existencia del guardia de seguridad.

  • Acoplamiento

    • El acoplamiento de las clases organizativas (School, ManagementBoard y Department) varía para cada tipo de clase organizativa. En el caso de la clase School, depende del número de entidades; y en el caso de las entidades ManagementBoard y Department, depende del tipo del profesor.

    • Al tratarse de una cantidad que varía para cada tipo de clase organizativa (ej. School, 2; ManagementBoard, 1; Department, 1), se considerará para el análisis la media de los acoplamientos de las clases que representan a los distintos tipos de clases organizativas (ej. 1,3).

    • Su resultado es de orden lineal, O(n).

AcoplamientoFineSingleton

AcoplamientoFineSingleton2

  • Granularidad

    • La granularidad de las clases organizativas (School, ManagementBoard y Department) depende de la cantidad de parámetros requeridos en su constructor. Dicha cantidad varía para cada tipo de participante (ej. School, 0; ManagementBoard, 0; Department, 0), por lo que se considerará para el análisis la media de todos los parámetros de los constructores de las clases que representan a los distintos tipos de clase organizativa (ej. 0).

    • Se calcula como la media de los parámetros de los constructores de las clases que representan a los distintos tipos de clase organizativa; es decir, la suma de los parámetros de los constructores de todas las clases que representan un tipo de clase organizativa (ej. 0 + 0 + 0 = 0) dividido entre el número de tipos de clases organizativas (ej. 3).

    • Su resultado es de orden lineal, O(n).

GranularidadFineSingleton

GranularidadFineSingleton2

Intención Aplicabilidad
  • La intención de este patrón es asegurar que una clase tenga una sola instancia y proveer un punto de acceso global para ésta.

  • Se requiere exactamente una única instancia de una clase y debe ser accesible a los clientes desde un punto de acceso bien conocido.

  • Se precisa que la única instancia sea extensible por subclasificación de tal manera que los clientes puedan ser capaces de usar la instancia extendida sin modificar su código,

Solución

Participantes Estructura General
  • Singleton: responsable de crear y almacenar su propia instancia única. Define una operación de clase getInstance que permite a los clientes acceder a su única instancia.

estructuraGeneralSingleton

Implementación

Variaciones
variacionesSingleton
  • Se puede subclasificar el Singleton para devolver una instancia polimórfica utilizando diversos mecanismos:

    • La utilización de variables de entorno y sentencias condicionales para determinar la subclase es más flexible, pero implica fuertes conexiones al conjunto de posibles clases Singleton.

    • Situar la implementación de getInstance fuera de la clase padre (por ejemplo, en una Factoría) o ponerla en la subclase. Esto permite decidir la clase de Singleton en tiempo de compilación.

    • La utilización de un registro de objetos Singleton. En lugar de definir el conjunto de posibles clases Singleton en getInstance, las clases Singleton pueden registrar su única instancia por su nombre en un registro conocido (por ejemplo, en su constructor).

Consecuencias

Ventajas Desventajas
  • Permite tener un control estricto sobre cómo y cuándo los clientes acceden a la instancia única de la clase.

  • Supone una mejora sobre el uso de variables globales ya que evita contaminar el espacio de nombres con variables globales que almacenen las instancias.

  • Permite tener subclases y es fácil configurar una aplicación con una instancia de una clase extendida que se necesite en tiempo de ejecución.

  • Facilita cambiar de opinión y permitir más de una instancia de la clase. Además, se puede utilizar el mismo enfoque para controlar el número de instancias que utiliza la aplicación, modificando únicamente la operación que otorga acceso a la instancia del Singleton.

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

PatronEstructural

adapter

bridge

composite

decorator

facade

flyweight

proxy

  • se ocupan de cómo se combinan las clases y los objetos para formar estructuras más grandes.

  • se basan en un mismo pequeño conjunto de mecaninsmos del lenguaje para estructurar el código: herencia de clases simple y múltiple y composición de objetos

  • tienen estruturas de clases muy similares que ocultan los diferentes propósitos de estos patrones.

Adapter

Problema

Motivación
motivacionAdapter
  • Cuando se viaja al extranjero, existen problemas al enchufar los aparatos eléctricos (secador de pelo, maquinilla de afeitar, …​) porque el número, forma y/o disposición de las patillas de los enchufes no son iguales en todos los países, ya que existen distintos estándares.

  • Un solución costosa y engorrosa sería comprar nada más llegar al destino todos los aparatos eléctricos con el estándar del país de destino y guardarlos en el trastero hasta que se vuelva a viajar a un país del mismo estándar.

  • Esto complica la comprensión de su comportamiento y tiende a crecer a medida que surgen nuevos países de destino

  • Cohesión

    • La cohesión de la clase Scenario no es adecuada puesto que asume varias responsabilidades dispares o que no le atañen, como la gestión de aparatos dependiendo del estándar de destino (creación de objetos).

  • Acoplamiento

    • El acoplamiento de la clase Scenario (ej. 6) depende del número de aparatos eléctricos y del número de distintos estándares.

    • Se calcula como el producto del número de estándares (ej. C y F, 2) por el número de aparatos eléctricos (ej. secador y maquinilla de afeitar, 2) más el enchufe (ej. PowerPointC o PowerPointF, 1).

    • Su resultado es de orden cuadrático, O(n2).

AcoplamientoWrongAdapter

AcoplamientoWrongAdapter2

  • Granularidad

    • La granularidad de la clase Scenario (ej. 6) depende del número de aparatos eléctricos y del número de distintos estándares.

    • Se calcula como el producto del número de estándares (ej. C y F, 2) por el número de aparatos eléctricos (ej. secador y maquinilla de afeitar, 2) más el enchufe (ej. PowerPointC o PowerPointF, 1).

    • Su resultado es de orden cuadrático, O(n2).

GranularidadWrongAdapter

GranularidadWrongAdapter2

Intención

intencionAdapter
  • Otra solución barata y sencilla es mantener los aparatos eléctricos del origen y únicamente comprar adaptadores antes de viajar a un destino de otro estándar y guardarlos a mano para el próximo viaje a un país con el mismo estándar.

  • Cohesión

    • La cohesión de la clase Scenario es más adecuada puesto que asume menos responsabilidad, centrada en los aparatos eléctricos con solo los adaptadores necesarios según los países de destino.

  • Acoplamiento

    • El acoplamiento de la clase Scenario (ej. 5) depende del número de aparatos eléctricos y del número de distintos estándares.

    • Se calcula como el producto de 2 por el número de estándares (ej. C y F, 2) menos 1 más el número de aparatos eléctricos (ej. secador y maquinilla de afeitar, 2).

    • Su resultado es de orden lineal, O(n).

AcoplamientoFineAdapter

AcoplamientoFineAdapter2

Granularidad

  • La granularidad de la clase Scenario (ej. 5) depende del número de aparatos eléctricos y del número de distintos estándares.

  • Se calcula como el producto de 2 por el número de estándares (ej. C y F, 2) menos 1 más el número de aparatos eléctricos (ej. secador y maquinilla de afeitar, 2).

  • Su resultado es de orden lineal, O(n).

GranularidadFineAdapter

GranularidadFineAdapter2

Intención Aplicabilidad
  • La intención de este patrón es convertir la interfaz de una clase en otra interfaz que el cliente espera, permitiendo trabajar clases conjuntamente que no podrían de otra manera por la incompatibilidad de sus interfaces.

  • Se quiere crear una clase reutilizable que coopere con clases no relacionadas o que no han sido previstas, es decir, clases que no tienen por qué tener interfaces compatibles.

    • Se quiere usar una clase existente (ej. una clase de una biblioteca que ha sido diseñada para reutilizarse) y su interfaz no concuerda con la que se necesita (específica del dominio que requiere la aplicación).

Solución

Estructura General

  • Adapter de clases

estructuraGeneralAdapterClases
  • Adapter de Objetos

EstructuraGeneralAdapterObjetos

Participantes

  • Target: declara una interfaz específica del dominio que usa el cliente.

  • Adaptee: declara una interfaz existente que necesita ser adaptada.

  • Adapter: clase que adapta la interfaz de Adaptee a la interfaz de Target.

  • Scenario: colabora con objetos que se ajustan a la interfaz Target, y utiliza el Adapter para poder colaborar con objetos que se ajusten a la interfaz Adaptee.

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
  • Adapter de clases: Permite que Adapter redefina parte del comportamiento de Adaptee, por ser Adapter una subclase de Adaptee. Introduce un solo objeto y no se necesita ningún puntero de indirección adicional para obtener el objeto adaptado.

  • Adapter de objetos: Permite que un mismo Adapter funcione con muchos Adaptee, es decir, con el Adaptee en sí y todas sus subclases, en caso de que las tenga. El Adapter puede añadir funcionalidad a todos los Adaptee a la vez.

  • Una clase es más reutilizable cuando minimizamos las asunciones que deben hacer otras clases para usarla. Al hacer la adaptación de interfaces en una clase estamos eliminando la asunción de que otras clases ven la misma interfaz. Dicho de otro modo, la adaptación de interfaces nos permite incorporar nuestra clase a sistemas existentes que podrían esperar interfaces diferentes a la de la clase.

  • Los adaptadores difieren en la cantidad de trabajo necesaria para adaptar Adaptee a la interfaz Target. Hay una amplia gama de tareas posibles, que van desde la simple conversión de interfaces (ej. cambiar los nombres de las operaciones), a implicar alguna tarea de encapsulación o modificación de los argumentos y de los tipos de retorno, o incluso a permitir un conjunto completamente nuevo de operaciones. La cantidad de trabajo que hace el Adapter depende de lo parecida que sea la interfaz de Target a la de Adaptee.

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
motivacionBridge
  • Un asesor se responsabiliza de asesorar mediante informes que presentan adecuadamente análisis de grandes volúmenes de datos con distinto nivel de detalle, superficial o formal o …, sobre distintas áreas, económica o fiscal o legal o …

  • Esto complica la comprensión de su comportamiento y tiende a crecer a medida que surgen nuevas áreas.

  • Cohesión

    • La cohesión de la clase Scenario no es adecuada puesto que asume varias responsabilidades dispares o que no le atañen, como presentar adecuadamente el análisis de la obtención de los datos sobre las distintas áreas.

    • Además, la cohesión se ve también perjudicada por la falta de centralización de las distintas áreas, que se encuentran replicadas en la jerarquía de herencias paralela existente en los analistas.

  • Acoplamiento

    • El acoplamiento de la clase Scenario (ej. 6) depende del número de niveles de detalle y del número de áreas.

    • Se calcula como el producto del número de niveles de detalle (ej. casual y formal, 2) por el número de áreas (ej. económica, fiscal y legal, 3).

    • Su resultado es de orden cuadrático, O(n2).

AcoplamentoWrongBridge

AcoplamentoWrongBridge2

  • Granularidad

    • La granularidad de la clase Scenario (ej. 6) depende del número de niveles de detalle y del número de áreas.

    • Se calcula como el producto del número de niveles de detalle (ej. casual y formal, 2) por el número de áreas (ej. económica, fiscal y legal, 3).

    • Su resultado es de orden cuadrático, O(n2).

GranularidadWrongBridge

GranularidadWrongBridge2

Intención
intencionBridge
  • Si la responsabilidad de asesor incluye únicamente la presentación de la información con distinto nivel de detalle (casual, formal, …) y otros, analistas, se responsabilizan por separado de cómo se obtiene la información para las distintas áreas, ninguna clase será compleja y se evitará la tendencia a crecer.

  • Cohesión

    • La cohesión de la clase Scenario es adecuada puesto que asume una única responsabilidad, centrada en el nivel de detalle y el tipo área, colaborando con el servicio del asesor y analista.

    • Se consigue así una centralización de las distintas áreas evitándose la replicación de código en jerarquías de herencias paralelas.

  • Acoplamiento

    • El acoplamiento de la clase Scenario (ej. 5) depende del número de niveles de detalle y del número de áreas.

    • Se calcula como la suma del número de niveles de detalle (ej. casual y formal, 2) más el número de áreas (ej. económica, fiscal y legal, 3).

    • Su resultado es de orden lineal, O(n).

AcoplamientoFineBridge

AcoplamientoFineBridge2

  • Granularidad

    • La granularidad de la clase Scenario (ej. 5) depende del número de niveles de detalle y del número de áreas.

    • Se calcula como la suma del número de niveles de detalle (ej. casual y formal, 2) más el número de áreas (ej. económica, fiscal y legal, 3).

    • Su resultado es de orden lineal, O(n).

GranularidadFineBridge

GranularidadFineBridge2

Intención Aplicabilidad
  • La intención de este patrón es desacoplar una abstracción de su implementación de tal forma que las dos pueden variar independientemente.

  • Evitar una relación estática entre una abstracción y su implementación. Por ejemplo, cuando debe seleccionarse o cambiarse la implementación en tiempo de ejecución.

  • Permitir que tanto las abstracciones como sus implementaciones sean extensibles mediante subclasificación. En este caso, el patrón Bridge permite multiplexar las opciones para las diferentes abstracciones y sus implementaciones, y extenderlas independientemente.

  • Evitar que los cambios en la implementación de una abstracción tengan un impacto en los clientes. Es decir, evitar que su código necesite ser recompilado.

  • Evitar una proliferación de clases en una jerarquía de herencia paralela.

  • Compartir una implementación entre varios objetos (tal vez usando un contador de referencias) y este hecho deba permanecer oculto al cliente.

Solución

Estructura General

estructuraGeneralBridge

Participantes

  • Abstraction: declara la interfaz de la abstracción, pudiendo proporcionar un comportamiento y una estructura estándar. Además, contiene una referencia a un objeto de tipo Implementor, que normalmente se fija por medio de un método set o a través del constructor.

  • RefinedAbstraction: clase derivada de Abstraction que extiende su interfaz proporcionando un comportamiento adicional o modificado.

  • Implementor: declara la interfaz de la implementación.

    • Esta interfaz no tiene por qué corresponderse exactamente con la de la abstracción. De hecho, suelen ser muy distintas, proporcionando la interfaz Implementor operaciones abstractas primitivas que la interfaz Abstraction utiliza para ofrecer operaciones de más alto nivel.

    • Es decir, todas las operaciones de las subclases de la abstracción se implementan en términos de operaciones abstractas de la interfaz del implementador. Esto desacopla las abstracciones de las diferentes implementaciones específicas de cada plataforma. Nos referimos a la relación entre la abstracción y la implementación como un puente, porque una abstracción con su implementación, permitiendo que ambas varíen de forma independiente.

  • ConcreteImplementor: clase derivada de Implementor que redefine su implementación concreta.

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
  • No une permanentemente una implementación a una interfaz, sino que la implementación puede configurarse en tiempo de ejecución. Incluso es posible que un objeto cambie su implementación en tiempo de ejecución.

    • El hecho de desacoplar Abstraction e Implementor también elimina de la implementación dependencias de tiempo de compilación. Ahora, cambiar una clase ya no requiere recompilar la clase Abstraction y sus clientes. Esta propiedad es esencial cuando debemos asegurar la compatibilidad binaria entre distintas versiones de una biblioteca de clases.

  • Se pueden extender las jerarquías de Abstraction y de Implementor de manera independiente.

  • Se puede aislar a los clientes de los detalles de implementación, como el comportamiento de objetos implementadores y el correspondiente mecanismo de conteo de referencias (si existe).

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
  • En muchos juegos de mesa (trivial pursuit, parchis, …​) hay limitaciones en el tablero para el número máximo de jugadores y, cuando hay más personas que este máximo, existe la posibilidad de formar equipos para que se entretengan todos.

  • Esto complica la comprensión de su comportamiento y tiende a crecer a medida que surgen nuevos tipos de equipos.

motivacionComposite
  • Cohesión

    • La cohesión de la clase Game no es adecuada puesto que asume varias responsabilidades dispares o que no le atañen, como la gestión de los distintos tipos de jugadores (personas y de equipos).

  • Acoplamiento

    • El acoplamiento de la clase Game (ej. 2) depende de la cantidad de distintos tipos de equipos y personas.

    • Se calcula como la suma de la cantidad de distintos tipos de personas (ej. Person, 1) más la cantidad de distintos tipos de equipos (ej. Team, 1).

    • Su resultado es de orden lineal, O(n).

AcoplamientoWrongComposite

AcoplamientoWrongComposite2

  • Granularidad

    • La granularidad de la clase Game (ej. 4) depende de la cantidad de métodos requeridos para los distintos tipos de equipos y personas.

    • Se calcula como las variaciones con repetición1 de la suma de la cantidad de distintos tipos de personas (ej. Person, 1) más la cantidad de distintos tipos de equipos (ej. Team, 1) tomados de dos en dos, con la simplificación de que el método play relativo a la responsabilidad de la clase Game admite solo 2 parámetros.

      • 1 Nótese que las variaciones con repetición de m elementos tomados de n en n da lugar a los posibles grupos formados por n elementos de manera que:

      • No entran todos los elementos si m> n. Sí pueden entrar todos los elementos si m ⇐ n.

        • Sí importa el orden.

        • Sí se repiten los elementos.

    • La fórmula para este tipo de variaciones es VR=m^n

  • Su resultado es de orden cuadrático, O(n 2).

GranularidadWrongComposite

GranularidadWrongComposite2

Intención
  • La solución más habitual es nombrar un representante del equipo y, de cara al resto de jugadores, se comporta como un único jugador. Internamente el representante se organizará transparentemente con el resto de personas del equipo para tomar decisiones.

itencionComposite
  • Cohesión

    • La cohesión de la clase Game es adecuada puesto que asume una única responsabilidad, centrada en la gestión de los jugadores sin prestar atención a su tipo.

  • Acoplamiento

    • El acoplamiento de la clase Game (ej. 1) depende de la cantidad de métodos requeridos para un único tipo de jugador (ej. Player, 1).

    • Su resultado es de orden constante, O(1).

AcoplamientoFineComposite

AcoplamientoFineComposite2

  • Granularidad

    • La granularidad de la clase Game (ej. 1) depende de un tipo de jugador (ej. Player, 1).

    • Su resultado es de orden constante, O(1).

GranularidadFineComposite

GranularidadFineComposite2

Intención Aplicabilidad
  • La intención de este patrón es componer objetos en estructuras arbóreas para representar las jerarquías de todo-parte y permitir a los clientes tratar con objetos individuales y objetos compuestos uniformemente.

  • Representar jerarquías de objetos parte-todo.

  • Permitir que los clientes sean capaces de obviar las diferencias entre composiciones de objetos y los objetos individuales. Los clientes tratarán a todos los objetos de la estructura compuesta de manera uniforme.

Solución

Estructura General

estructuraGeneralComposite

Participantes

  • Component: declara la interfaz de los objetos de la composición, proporcionando, si es posible, implementaciones predeterminadas para las operaciones comunes a las clases Leaf y Composite.

    • Opcionalmente:

      • Declara una interfaz para acceder a sus componentes hijos y gestionarlos.

      • Define una interfaz para acceder al padre de un componente en la estructura recursiva y, si es necesario, la implementa.

  • Leaf: clase derivada de Component que define el comportamiento de los objetos primitivos de la composición. Las clases Leaf no tienen hijos.

  • Composite: clase derivada de Component que define el comportamiento de los objetos que tienen hijos. Por lo tanto, almacena los componentes hijos e implementa las operaciones relacionadas con ellos.

    • Muchos diseños especifican una ordenación de los hijos de Composite. Cuando la ordenación de los hijos es una cuestión a tener en cuenta, se deben diseñar las interfaces de acceso y gestión de hijos cuidadosamente para controlar la secuencia.

  • Director: clase que manipula los objetos de la composición a través de la interfaz Component.

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.

variasionesComposite
  • 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
  • Define jerarquías de clases formadas por objetos primitivos y compuestos. Los objetos primitivos pueden componerse en otros objetos más complejos, que a su vez pueden ser compuestos y así de manera recursiva. Allí donde el código espere un objeto primitivo, también podrá recibir un objeto compuesto.

  • Los clientes pueden tratar uniformemente a las estructuras compuestas y a los objetos individuales. Los clientes normalmente no conocen (y no les debería importar) si están tratando con una hoja o con un componente compuesto. Esto simplifica el código del cliente, puesto que evita tener que escribir funciones con instrucciones if anidadas en las clases que definen la composición

  • Facilita añadir nuevos tipos de componentes. Si se definen nuevas subclases Composite o Leaf, éstas funcionarán automáticamente con las estructuras y el código cliente existentes. No hay que cambiar los clientes para las nuevas subclases de Component.

  • Puede hacer que un diseño sea demasiado general. La desventaja de facilitar añadir nuevos componentes es que hace más difícil restringir los componentes de un compuesto. A veces se requiere que un compuesto sólo tenga ciertos componentes. Con el patrón Composite, no podemos confiar en el sistema de tipos para que haga cumplir estas restricciones, por lo que es necesario realizar comprobaciones en tiempo de ejecución.

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
motivacionDecorator
  • Un maestro de repostería se responsabiliza de elaborar postres (_galletas tradicionales, soufflé, …) para lo que bate y hornea con diversos utensilios (horno, reloj, …) con diferentes técnicas específicas (cambios de ritmo, cambios de temperatura, …). En sus labores, se apoya en un aprendiz que sólo sabe batir y hornear como le digan, al que debe supervisar constantemente, lo cual disturba al maestro para investigar e innovar._

  • Esto complica la comprensión de su comportamiento y tiende a crecer a medida que surgen nuevos tipos de postres.

  • Cohesión

    • La cohesión de la clase MasterPastryChef no es adecuada puesto que asume varias responsabilidades dispares o que no le atañen, como la supervisión del aprendiz mientras elabora los postres con diversos utensilios e investiga e innova.

  • Acoplamiento

    • El acoplamiento de la clase MasterPastryChef (ej. 2) depende del aprendiz y de los distintos tipos de utensilios.

    • Se calcula como la suma de 1 del aprendiz (ej. 1) más la cantidad de distintos tipos de utensilios (ej. Oven, Clock, 2).

    • Su resultado es de orden lineal, O(n).

AcoplamientoWrongDecorator

AcoplamientoWrongDecorator2

  • Granularidad

    • La granularidad de la clase MasterPastryChef (ej. 6) depende de la cantidad de métodos requeridos para los distintos postres y las distintas técnicas.

    • Se calcula como el producto de la cantidad de distintos postres (ej. TraditionalCookies, Souffle, 2) por 1 más la cantidad de distintos tipos de técnicas (ej. beat, bake, 2).

    • Su resultado es de orden cuadrático, O(n 2).

GranularidadWrongDecorator

GranularidadWrongDecorator2

  • También interesa analizar otro aspecto relacionado con la granularidad, en este caso, en la clase Scenario, ya que es posible que se desee elaborar cualquier combinación de los distintos postres.

  • Es decir, la granularidad de la clase Scenario medida como el número de llamadas a métodos de la clase MasterPastryChef en función de la cantidad de distintos postres.

  • Se calcula como la suma del aprendiz (ej. ApprenticePastryChef) y las combinaciones2 de la cantidad de distintos postres tomadas de n en n, variando n desde uno hasta el número de postres.

GranularidadWrongDecorator3

GranularidadWrongDecorator4

  • 2 Nótese que las combinaciones de m elementos tomados de n en n (m >= n) da lugar a los posibles grupos formados por n elementos de manera que:

    • No entran todos los elementos si m > n. Sí pueden entrar todos los elementos si m = n.

    • No importa el orden.

    • No se repiten los elementos.

  • La fórmula para este tipo de variaciones es Cm,n = m! / (n! * (m-n)! )

Intención
itencionDecorator
  • Una alternativa es invertir en la contratación de oficiales de cocina específicos que se responsabilizan de elaborar postres (_galletas tradicionales, soufflé, …) supervisando al aprendiz y permitiendo al maestro centrarse en la investigación e innovació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.
  • Acoplamiento

    • El acoplamiento de la clase MasterPastryChef (ej. 1) depende del tipo del repostero (ej. PastryChef, 1).

    • Su resultado es de orden constante, O(1).

AcoplamientoFineDecorator

AcoplamientoFineDecorator2

  • Granularidad

    • La granularidad de la clase MasterPastryChef (ej. 1) depende únicamente del tipo de repostero.

    • Su resultado es de orden constante, O(1).

GranularidadFineDecorator

GranularidadFineDecorator2

  • También interesa analizar otro aspecto relacionado con la granularidad, en este caso, en la clase Scenario, ya que es posible que se desee elaborar cualquier combinación de los distintos postres.

  • Es decir, la granularidad de la clase Scenario medida como el número de llamadas a métodos de la clase MasterPastryChef en función de la cantidad de distintos postres.

  • Se calcula como la suma del aprendiz (ej. ApprenticePastryChef) y el método relativo a la responsabilidad de la clase (ej. preparePastries).

GranularidadFine2Decorator3

GranularidadFine2Decorator4

Intención Aplicabilidad
  • La intención de este patrón es agregar responsabilidades adicionales a un objeto dinámicamente, proveyendo una alternativa flexible a la subclasificación para extender la funcionalidad.

  • Añadir responsabilidades a objetos individuales en vez de a toda una clase, sin afectar a otros objetos.

  • Añadir responsabilidad que pueda ser retirada.

  • Evitar la extensión mediante herencia cuando ésta no es viable debido a:

    • Existencia de un gran número de extensiones independientes, que producirían una explosión de subclases para permitir todas las combinaciones.

    • Necesidad de utilizar una clase cuya definición esté oculta o no esté disponible para ser heredada.

Solución

Estructura General

estructuraGeneralDecorator

Participantes

  • Component: declara la interfaz de los objetos a los que se puede añadir responsabilidades dinámicamente.

    • La existencia de esta clase garantiza una interfaz compatible entre los componentes y los decoradores.

    • Es importante que esta clase común se mantenga ligera; es decir, debería centrarse en definir una interfaz, no en guardar datos. La definición de cómo se representan los datos debería delegarse a las subclases. De no ser así, la complejidad de la clase Component puede hacer que los decoradores sean demasiado pesados como para usar un gran número de ellos e incrementa la probabilidad de que las subclases concretas estén pagando por características que no necesitan.

  • ConcreteComponent: clase derivada de Component que define un objeto al que se pueden añadir responsabilidades adicionales.

  • Decorator: clase derivada de Component, por lo que define una interfaz que se ajusta a la interfaz de ésta, de manera que su presencia es transparente a sus clientes. Esta transparencia permite anidar decoradores recursivamente, consiguiéndose así un número de responsabilidades añadidas. Además, define los comportamientos estándar que se esperan de todas las clases ConcreteDecorator, tales como:

    • Mantener una referencia a un objeto Component, que podrá ser un objeto ConcreteComponent u otro Decorator.

    • Reenviar las peticiones al componente y realizar, opcionalmente, acciones adicionales antes o después del reenvío.

  • ConcreteDecorator: clase derivada de Decorator que añade alguna responsabilidad al componente. Un ConcreteDecorator puede definir métodos adicionales y/o variables para ampliar al componente.

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
  • El patrón Decorator ofrece una manera más flexible de añadir responsabilidades a los objetos que la utilización de la herencia (múltiple) estática. Con los decoradores se pueden añadir y eliminar responsabilidades en tiempo de ejecución. Por el contrario, la herencia requiere crear una nueva clase para cada responsabilidad adicional. Esto da lugar a muchas clases diferentes e incrementa la complejidad de un sistema. Por otro lado, utilizar diferentes clases ConcreteDecorator para una determinada clase ConcreteComponent permite mezclar responsabilidades.

  • Ofrece un enfoque para añadir responsabilidades que consiste en pagar sólo por aquello que se necesita. En vez de intentar permitir todas las funcionalidades inimaginables en una clase compleja y adaptable, se puede definir una clase simple y añadir luego funcionalidad incrementalmente con objetos ConcreteDecorator. La funcionalidad puede obtenerse componiendo partes simples. Como resultado, una aplicación no necesita pagar por características que no usa.

    • También resulta sencillo definir nuevos tipos de ConcreteDecorator independientemente de las clases de objetos de las que hereden, incluso para extensiones que no hubieran sido previstas. Extender una clase compleja tiende a exponer detalles no relacionados con las responsabilidades que se están añadiendo.

  • Un decorador se comporta como un revestimiento transparente, lo cual permite que los decoradores aparezcan en cualquier lugar en el que pueda ir un componente. Pero desde el punto de vista de la identidad de un objeto, un componente decorado no es idéntico al componente en sí. Por tanto, no deberíamos apoyarnos en la identidad de objetos cuando se estén usando decoradores.

  • Un diseño que usa el patrón Decorator suele dar como resultado sistemas formados por muchos objetos pequeños muy parecidos. Los objetos sólo se diferencian en la forma en que están interconectados, y no en su clase o en el valor de sus variables. Aunque dichos sistemas son fáciles de adaptar por parte de quienes los comprenden bien, pueden ser difíciles de aprender y de depurar.

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
motivacionFacade
  • Una obra de reforma de un domicilio conlleva que el propietario realice diversas tareas burocráticas (solicitar crédito en el banco, solicitar licencia en el ayuntamiento, …) y, en particular, contratar a diversos especialistas (albañil, carpintero, electricista, fontanero, pintor, …) y coordinarlos a todos (tiempos, retrasos, materiales, precondiciones, …).

  • Esto es muy complejo y tiende a crecer a medida que surgen nuevos colaboradores especialistas (experto en domótica, decorador, …).

  • Cohesión

    • La cohesión de la clase Owner no es adecuada puesto que asume varias responsabilidades dispares o que no le atañen, como la contratación de todos y cada uno de los especialistas necesarios para la reforma del domicilio.

  • Acoplamiento

    • El acoplamiento de la clase Owner (ej. 7) depende del número de relaciones por tareas burocráticas y contratación de especialistas.

    • Se calcula como la suma de la cantidad de relaciones burocráticas (ej. Bank y TownHall, 2) más la cantidad de contrataciones de especialistas (ej. BrickLayer, Carpenter, …, 5).

    • Su resultado es de orden lineal, O(n).

AcoplamentoWrongFacade

AcoplamentoWrongFacade2

  • Granularidad

    • La granularidad del método performAlteration de la clase Owner (ej. 7) depende del número de relaciones por tareas burocráticas y contratación de especialistas.

    • Se calcula como la suma de la cantidad de operaciones burocráticas (ej. Bank y TownHall, 2) más la cantidad de contrataciones con especialistas (ej. BrickLayer y Carpenter, …, 5).

    • Su resultado es de orden lineal, O(n).

GranularidadWrongFacade

GranularidadWrongFacade2

Intención
intencionFacade
  • Una alternativa más sencilla y que no tiende a crecer es que el propietario se centre en sus tareas y contrate a un contratista para no interactuar con el resto de especialistas.

  • Cohesión

    • La cohesión de la clase Owner es adecuada puesto que asume una única responsabilidad, centrada en la reforma mediante la contratación de unos pocos servicios, sin entrar en detalles.

  • Acoplamiento

    • El acoplamiento de la clase Owner (ej. 3) depende del número de relaciones por tareas burocráticas y del contratista.

    • Se calcula como la suma de la cantidad de relaciones burocráticas (ej. Bank y TownHall, 2) más un contratista (ej. Contractor, 1).

    • Su resultado es de orden lineal, O(n).

AcoplamientoFineFacade

AcoplamientoFineFacade2

  • Granularidad

    • La granularidad del método performAlteration de la clase Owner (ej. 3) depende del número de relaciones por tareas burocráticas y del contratista.

    • Se calcula como la suma de la cantidad de operaciones burocráticas (ej. Bank y TownHall, 2) más una contratación con el contratista (ej. Contractor, 1).

    • Su resultado es de orden lineal, O(n).

GranularidadFineFacade

GranularidadFineFacade2

Intención Aplicabilidad
  • La intención de este patrón es proveer de una interfaz unificada a un conjunto de interfaces de un subsistema definiendo una interfaz de más alto nivel que hace más fácil de usar el subsistema.

  • Simplificar el uso de sistemas complejos proporcionando una interfaz más sencilla sin eliminar las opciones avanzadas. De esta manera puede proveer una vista simple por defecto que es suficiente para la mayoría de los clientes, y solo los clientes que necesiten más particularizaciones necesitarán mirar tras la fachada.

  • Reducir el acoplamiento entre los clientes y las implementaciones de las clases de un subsistema cuando hay muchas dependencias entre ambos. Al desacoplar el subsistema de los clientes y otros subsistemas, promociona la independencia del subsistema y la portabilidad.

  • Organizar por capas los subsistemas. Permite definir un punto de entrada para cada nivel de subsistema. Si los subsistemas son dependientes, se pueden simplificar las dependencias entre ellos haciéndolos comunicarse entre sí solamente a través de sus fachadas.

Solución

Participantes Estructura General
  • Facade: clase que delega las peticiones de los clientes en los objetos SubsystemClass apropiados, ya que sabe qué clases del subsistema son las responsables ante una petición.

  • SubsystemClass: implementan la funcionalidad del subsistema y llevan a cabo las peticiones solicitadas por el objeto Facade, aunque para ellas, el objeto Facade sólo será otro cliente más, al que no conocen y del que no tienen una referencia.

estructuraGeneralFacade

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
  • Promueve un débil acoplamiento entre el subsistema y sus clientes:

    • Los subsistemas a menudo obtienen complejidad según evolucionan. La mayoría de los patrones, cuando se aplican, provocan más clases más pequeñas. Esto hace del subsistema más reusable y fácil de personalizar, pero también llega a ser más duro de usar por los clientes que no necesiten particularizaciones.

    • Blinda a los clientes de componentes del subsistema, reduciendo así el número de objetos con los que tratan y haciendo que el subsistema sea más fácil de usar.

    • La reducción de las dependencias de compilación es vital en grandes sistemas de software.

  • Ayuda a la organización por capas de un sistema. Todos los subsistemas pueden tener su propio Facade para reducir el acoplamiento entre ellos.

  • Simplifica el traslado de los sistemas a otras plataformas.

  • No impide que las aplicaciones utilicen clases del subsistema si lo precisan. De este modo se puede elegir entre la facilidad de uso y la generalidad.

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
  • En un torneo de cartas se disputan mil juegos, y cada juego se responsabiliza de crear las 52 cartas de la baraja que precisa, por lo que se necesitará almacenar una gran cantidad de objetos iguales, es decir, 52.000 cartas (mil ases de picas, mil reyes de corazones, …) aunque éstas sean constantes y no cambien.

motivacionFlyweight
  • Cohesión

    • La cohesión de la clase CardGame no es adecuada puesto que asume varias responsabilidades dispares o que no le atañen, como las reglas del juego y la obtención de las cartas (creación de objetos).

  • Acoplamiento

    • El acoplamiento de la clase CardGame (ej. 3) depende del número de subclases de cartas.

    • Se calcula como la suma de la clase abstracta carta (ej. Card, 1) más el número de subclases (ej. StandardCard y WildCard, 2).

    • Su resultado es de orden lineal, O(n).

AcoplamentoWrongFlyweight

AcoplamentoWrongFlyweight2

  • Granularidad

    • La granularidad de la clase CardGame (ej. 2) depende de la cantidad de métodos requeridos.

    • Se calcula como la cantidad de métodos relativos para la gestión de cartas (ej. getCard con y sin parámetros, 2).

    • Su resultado es de orden lineal, O(n.

GranularidadWrongFlyweight

GranularidadWrongFlyweight2

Intención
itencionFlyweight
  • Si la responsabilidad del juego incluye únicamente la solicitud de cartas, y otro, fábrica de cartas, se responsabiliza por separado de crear una única carta de cada palo y valor para ser reutilizada por todos los juegos, lo cual reducirá el número de objetos utilizados.

Cohesión

  • La cohesión de la clase CardGame es adecuada puesto que asume una única responsabilidad, centrada en las reglas del juego, colaborando con las cartas suministradas por la fábrica.

  • Acoplamiento

    • El acoplamiento de la clase CardGame (ej. 5) depende de la fábrica de cartas y del tipo y número de subclases de cartas.

    • Se calcula como la suma de la factoría de cartas (ej. CardFactory, 1) más la clase que almacena el estado extrínseco (ej. PlayingCard, 1) más la clase del tipo de carta (ej. Card, 1) más el número de subclases (ej. StandardCard y WildCard, 2).

    • Su resultado es de orden lineal, O(n).

AcoplamientoFineFlyweight

AcoplamientoFineFlyweight2

  • Granularidad

    • La granularidad de la clase CardGame (ej. 3) depende de la cantidad de métodos requeridos.

    • Se calcula como la cantidad de métodos relativos para la gestión de cartas (ej. getCard con y sin parámetros y setCard, 3).

    • Su resultado es de orden lineal, O(n).

GranularidadFineFlyweight

GranularidadFineFlyweight2

Intención Aplicabilidad
  • La intención de este patrón es promocionar la compartición para soportar un gran número de objetos de grano fino eficientemente.

  • Aplicable cuando se cumplen todas las siguientes condiciones:

    • Se utilizan un gran número de objetos idénticos o casi idénticos, existiendo un alto coste de almacenamiento debido a la gran cantidad de objetos.

    • Para todos aquellos objetos casi idénticos, las partes diferentes pueden ser separadas de las partes similares, permitiendo la compartición de las partes comunes.

      • El estado intrínseco consiste en la información que es independiente del contexto, por lo que se puede compartir.

      • El estado extrínseco depende y varía con el contexto, por lo que no puede ser compartido. Los clientes son responsables de la transmisión del estado extrínseco cuando éste sea necesario.

    • Muchos grupos de objetos pueden ser reemplazados por relativamente pocos objetos compartidos una vez que se elimina el estado extrínseco.

      • La extracción del estado extrínseco no ayudará a reducir los costos de almacenamiento si hay muchos tipos diferentes de estado extrínseco. Idealmente, el estado extrínseco puede ser calculado a partir de una estructura de objetos separada, con un requisito de almacenamiento mucho más pequeño.

    • La aplicación no depende de la identidad de un objeto. Dado que los objetos pueden ser compartidos, las comprobaciones de identidad devolverán cierto para objetos conceptualmente distintos.

Solución

Estructura General

estructuraGeneralFlyweight

Participantes

  • FlyWeight: declara una clase abstracta o interfaz a través de la cual los pesos ligeros pueden recibir un estado extrínseco y actuar sobre él.

  • ConcreteFlyWeight: clase derivada de FlyWeight que implementa los métodos de la interfaz y permite almacenar el estado intrínseco, en caso de que lo haya. Un objeto ConcreteFlyWeight debe poder ser compartido, por lo que cualquier estado que almacene debe ser independiente del contexto del objeto concreto. Es decir, un objeto ConcreteFlyWeight no puede hacer suposiciones sobre el contexto en el que opera.

  • UnsharedConcreteFlyWeight: no todas las subclases de FlyWeight necesitan ser compartidas, ya que la interfaz FlyWeight permite la compartición, pero no fuerza a ella. Los objetos UnsharedConcreteFlyWeight suelen tener objetos ConcreteFlyWeight como hijos en algún nivel de la estructura de objetos.

  • FlyWeightFactory: clase para la creación y el control de los objetos ConcreteFlyWeight, que garantiza que éstos se compartan de manera adecuada. Cuando se solicita un ConcreteFlyWeight, la clase FlyWeightFactory proporciona una instancia concreta o crea una instancia nueva en caso de que no exista ninguna.

    • Habitualmente, la clase FlyWeightFactory utiliza un array asociativo por algún código para almacenar los objetos ConcreteFlyWeight disponibles.

    • Esto implica algún tipo de recuento de referencias o recogida de basura para el almacenamiento de un peso mosca cuando ya no es necesario. Sin embargo, si el número de pesos mosca es fijo y pequeño puede ser interesante mantenerlos de forma permanente.

  • Director: clase que mantiene una referencia a los objetos FlyWeight y calcula o guarda el estado extrínseco de los mismos.

Implementación

Variaciones
  • No existen variaciones del patrón.

Consecuencias

Ventajas

  • Se consigue un ahorro de almacenamiento, cuyo volumen es una función de varios factores: la reducción en el número total de instancias lograda mediante la compartición; la cantidad de estado intrínseco por objeto; y si el estado extrínseco se calcula o se almacena.

  • Este ahorro en el caso del torneo de cartas sería el siguiente:

    • Almacenamiento Wrong

      • El número de objetos carta estándar en la clase Scenario (ej. 104) será el producto del número de juegos simultáneos (ej. 2) por el número de cartas estándar (ej. StandardCard, 52). Su resultado es de orden cuadrático, O(n2).

      • Cada carta estándar almacena 3 atributos: suit, value e isFacedUp.

    • Almacenamiento Fine

      • El número de objetos carta estándar en la clase Scenario (ej. 52) será el número de cartas estándar (ej. StandardCard, 52). Su resultado es de orden lineal, O(n).

      • Cada carta estándar almacena 2 atributos: suit y value.

      • Para almacenar el estado extrínseco de las cartas, el número de objetos en la clase Scenario (ej. 104) será el producto del número de juegos simultáneos (ej. 2) por el número de cartas (ej. PlayingCard, 52). Su resultado es de orden cuadrático, O(n2).

      • Cada objeto almacena 1 atributo: isFacedUp.

      • En la siguiente gráfica se compara el número de atributos que es necesario almacenar en ambos casos:

AtributosWrongFlayweight

AtributosWrongFlayweight2

Desventajas

  • Puede introducir costes de tiempo de ejecución asociados con la transferencia, búsqueda y/o computación del estado extrínseco.

  • El acoplamiento entre clases empeora al introducirse nuevas clases requeridas por el patrón para facilitar la compartición.

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
  • Un jefe se responsabiliza de atender las peticiones de sus empleados, concesión de vacaciones, subidas de sueldos, … pero puede necesitar ausentarse, en cuyo caso dejará desatendidas sus responsabilidades. Esto provoca una dependencia innecesaria en la localización del jefe para que todo siga su curso.

motivacionProxy
  • Cohesión

    • La cohesión de la clase Employee es adecuada puesto que asume una única responsabilidad, centrada en sus responsabilidades.

  • Acoplamiento

    • El acoplamiento de la clase Employee (ej. 1) depende del tipo del jefe (ej. Boss, 1).

    • Su resultado es de orden constante, O(1).

AcoplamientoWrongProxy

AcoplamientoWrongProxy1

  • Granularidad

    • La granularidad de la clase Employee (ej. 4) depende de la cantidad de métodos requeridos.

    • Se calcula como la suma de los métodos relativos a la responsabilidad de la clase (ej. requestHolidays, requestSalaryIncrease, requestDoctorAppointment y requestInvoiceRefund, 4).

    • Su resultado es de orden lineal, O(n).

GranularidadProxy

GranularidadProxy2

Intención
  • Si se nombra a un representante en ausencia del jefe para que centralice las peticiones de los empleados, y las responda siguiendo las instrucciones del jefe, las responsabilidades del jefe no quedarán desatendidas y se conseguirá una independencia de la localización del jefe.

intencionProxy
  • Cohesión

    • La cohesión de la clase Employee es adecuada puesto que asume una única responsabilidad, centrada en sus responsabilidades.

  • Acoplamiento

    • El acoplamiento de la clase Employee (ej. 1) depende del tipo del jefe (ej. Boss, 1).

    • Su resultado es de orden constante, O(1).

AcoplamientoFineProxy

AcoplamientoFineProxy2

  • Granularidad

    • La granularidad de la clase Employee (ej. 4) depende de la cantidad de métodos requeridos.

    • Se calcula como la suma de los métodos relativos a la responsabilidad de la clase (ej. requestHolidays, requestSalaryIncrease, requestDoctorAppointment y requestInvoiceRefund, 4).

    • Su resultado es de orden lineal, O(n).

GranularidadFineProxy

GranularidadFineProxy2

Intención Aplicabilidad
  • La intención de este patrón es proporcionar un sustituto o representante de otro objeto para controlar el acceso al mismo.

  • Existe una necesidad de una referencia a un objeto más versátil o sofisticada que un simple puntero:

    • Un Proxy virtual permite crear objetos costosos por encargo, para diferir todo el coste de su creación e inicialización hasta que sea realmente necesario usarlo.

    • Un Proxy remoto proporciona un representante local de un objeto situado en otro espacio de direcciones.

    • Un Proxy de protección controla el acceso al objeto original, lo cual resulta útil cuando los objetos deberían tener diferentes permisos de acceso.

    • Una referencia inteligente es un Proxy de un simple puntero que lleva a cabo operaciones adicionales cuando se accede a un objeto: contar el número de referencias para poder liberar el objeto si no existen referencias a él, cargar un objeto en memoria cuando es referenciado por primera vez, comprobar que se bloquea el objeto real antes de acceder a él para garantizar que no pueda ser modificado por ningún otro objeto, …

Solución

Participantes

Estructura General

  • Subject: declara una interfaz común para RealSubject y Proxy, de modo que pueda utilizarse un Proxy en cualquier sitio en el que se espere un RealSubject.

  • RealSubject: clase que implementa la interfaz Subject y define el objeto real representado por el objeto Proxy.

  • Proxy: clase que implementa la interfaz Subject y puede sustituir al objeto RealSubject, redirigiendo las llamadas de los métodos a éste cuando sea apropiado.

    • Mantiene una referencia que le permite acceder a RealSubject.

    • Los Proxy virtuales pueden guardar información adicional sobre RealSubject, por lo que pueden retardar el acceso al mismo cuando se recibe una petición.

    • Los Proxy remotos son responsables de codificar una petición para enviársela a RealSubject cuando éste se encuentra en un espacio de direcciones diferente.

    • Los Proxy de protección comprueban que el Director tenga los permisos de acceso necesarios para realizar una petición a RealSubject.

estructuraGeneralProxy

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
  • Introduce un nivel de indirección al acceder a un objeto, lo cual tiene muchos posibles usos dependiendo del tipo de Proxy:

    • En el caso del Proxy virtual permite diferir la creación del objeto real hasta que éste es necesario. Además, permite realizar optimizaciones sobre cuándo y cómo crear el objeto real.

    • En el caso del Proxy remoto permite ocultar el hecho de que el objeto real reside en un espacio de direcciones diferente, es decir, se puede ocultar la red al cliente.

    • En el caso del Proxy de protección se puede establecer el control de acceso y realizar tareas de mantenimiento adicionales cuando se accede al objeto.

  • Un inconveniente potencial del Proxy remoto es que, como el cliente no sabe que está trabajando sobre una red, podría no estar preparado para las penalizaciones de tiempo que puedan ocurrir.

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

PatronComportamiento

chainOfResponsibility

command

interpreter

iterator

mediator

memento

observer

state

strategy

templateMethod

visitor

  • Tienen que ver con algoritmos y con la asignación de responsabilidades a objetos para describir el flujo de control complejo que es difícil de seguir en tiempo de ejecución, lo que permite olvidarse de éste para concentrarse simplemente en el modo en que interconectan los objetos.

  • Encapsular aquello que puede variar es el tema de muchos patrones de comportamiento. Cuando un determinado aspecto de un programa cambia con frecuencia, estos patrones definen un objeto que encapsula dicho aspecto. La mayoría de los patrones tienen dos tipos de objetos: el nuevo objeto que encapsula el aspecto y el objeto existente que usa el nuevo objeto creado. Normalmente, si no fuera por el patrón, la funcionalidad de los nuevos objetos sería una parte integral de los existentes.

Chain of Responsibility

Problema

Motivación
motivacionChainOfResponsibility
  • Un alumno se responsabiliza de interponer una reclamación cuando tiene alguna queja, sobre un examen o una regla de un departamento o …, pero dependiendo del tipo de reclamación, deberá conocer y discernir a qué miembro de la cadena de responsabilidad debe presentar la reclamación, profesor o coordinador de la asignatura o director de departamento o jefe de estudios o director de escuela o rector o defensor del alumno …

  • Esto complica la comprensión de su comportamiento y tiende a crecer a medida que surgen nuevos miembros de la cadena de responsabilidad.

  • Cohesión

    • La cohesión de la clase Student no es adecuada puesto que asume varias responsabilidades dispares o que no le atañen, como discernir a qué miembro de la cadena de responsabilidad debe presentar cada reclamación.

  • Acoplamiento

    • El acoplamiento de la clase Student (ej. 7) depende del número de miembros de la cadena de responsabilidad.

    • Se calcula como la suma de las clases miembros de la cadena de responsabilidad (ej. Professor, SubjectCoordinator, DepartmentHeadMaster, …, 7).

    • Su resultado es de orden lineal, O(n).

AcoplamientoWrongChainOfResponsibility

AcoplamientoWrongChainOfResponsibility2

  • Granularidad

    • La granularidad de los métodos de la clase Student (ej. 7) depende del número de miembros de la cadena de responsabilidad.

    • Se calcula como la suma de los miembros de la cadena de responsabilidad (ej. Professor, SubjectCoordinator, DepartmentHeadMaster, …, 7).

    • Su resultado es de orden lineal, O(n).

GranularidadWrongCainOfResponsibility

GranularidadWrongCainOfResponsibility2

Intención
intencionChainOfResponsibility
  • Si la responsabilidad de estudiante incluye únicamente la interposición de la reclamación ante el primer miembro de la cadena de responsabilidad y otros, miembros de la cadena, se responsabilizan por separado de elevar la reclamación al siguiente miembro de la cadena cuando no pueden/saben resolverla, ninguna clase será compleja y se evitará la tendencia a crecer.

  • Cohesión

    • La cohesión de la clase Student es adecuada puesto que asume una única responsabilidad, centrada en la interposición de la reclamación, colaborando únicamente con el servicio del primer miembro de la cadena de responsabilidad, que a su vez se apoyará en el servicio prestado por el resto de miembros de la cadena para elevar la reclamación cuando sea preciso.

  • Acoplamiento

    • El acoplamiento de la clase Student (ej. 1) depende del tipo de la clase resolutora (ej. ClaimSolver, 1).

    • Su resultado es de orden constante, O(1).

AcoplamientoFineChainOfResponsibility

AcoplamientoFineChainOfResponsibility2

Granularidad

  • La granularidad de los métodos de la clase Student (ej. 1) depende del tipo de la clase resolutora (ej. ClaimSolver, 1).

  • Su resultado es de orden constante, O(1).

GranularidadFineCainOfresponsibility

GranularidadFineCainOfresponsibility2

Intención Aplicabilidad
  • La intención de este patrón es evitar acoplar al emisor de una petición a su receptor, dando a más de un objeto la posibilidad de responder la petición. Se basa en encadenar los objetos receptores y pasar la petición a lo largo de la cadena hasta que es procesada por algún objeto.

  • 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.

Solución

Participantes

Estructura General

  • Handler: define una interfaz para tratar las peticiones.

    • Opcionalmente implementa el enlace al sucesor.

  • ConcreteHandler: clase derivada de Handler que trata las peticiones de las que es responsable. Si puede manejar la petición, lo hace; en caso contrario, la reenvía a su sucesor.

  • Director: clase que inicia la petición a un objeto _ConcreteHandler- de la cadena, habitualmente, el primero.

    • No tiene conocimiento explícito de quién tratará la petición – se dice que la petición tiene un receptor implícito.

estructuraGenralChainOfResponsibility

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
  • Libera a un objeto de tener que saber qué otro objeto maneja una petición. Un objeto sólo tiene que saber que una petición será manejada “de forma apropiada”. 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.

    • Como resultado, se puede simplificar las interconexiones entre objetos. En vez de que los objetos mantengan referencias a todos los posibles receptores, solo tienen una única interfaz a su sucesor.

  • Ofrece una flexibilidad añadida para repartir responsabilidades entre objetos. Se pueden añadir o cambiar responsabilidades para tratar una petición modificando la cadena en tiempo de ejecución. Esto se puede combinar con la herencia para especializar los manejadores estáticamente.

  • Dado que las peticiones no tienen un receptor explícito, no hay garantía de que sean manejadas ya que la petición puede alcanzar el final de la cadena sin haber sido procesada.

  • En caso de haber un gran número de mensajes y muchas etapas de redireccionamiento en la cadena, podría tener lugar un alto intercambio baldío de mensajes, por lo que el rendimiento del sistema podría degradarse.

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
motivacionCommand
  • En un restaurante hay distintos tipos de empleados: camareros, cocineros, bármanes, … Un camarero se responsabiliza de atender las comandas de un cliente con el mismo algoritmo, informar al cliente de las opciones disponibles en la carta y sus ingredientes, anotar la comanda, dirigirse al interior (a la cocina o a la barra o …) para encargar la comanda al empleado adecuado (que sepa prepararla o …), esperar a que prepare la comanda, servir la comanda al cliente, … pero dependiendo de la opción de la carta seleccionada por el cliente, solicitará distintas preparaciones a distintos tipos de empleado, cocinero o barman o

  • Esto complica la comprensión de su comportamiento y tiende a crecer a medida que surgen nuevas opciones en la carta.

  • Cohesión

    • La cohesión de la clase Waiter no es adecuada puesto que asume varias responsabilidades dispares o que no le atañen, como la localización del tipo de empleado adecuado para preparar la comanda y las preparaciones necesarias para cada opción de la carta.

  • Acoplamiento

    • El acoplamiento de la clase Waiter (ej. 2) depende del número de tipos de empleados encargados de preparar comandas.

    • Se calcula como la suma del número de tipos de empleados encargados de preparar comandas (ej. Chef y Barman, 2).

    • Su resultado es de orden lineal, O(n).

AcoplamientoWrongCommand

AcoplamientoWrongCommand2

  • Granularidad

    • La granularidad de la clase Waiter (ej. 5) depende de la cantidad de métodos requeridos para las distintas opciones de la carta.

    • Se calcula como la suma de los métodos relativos a la responsabilidad de la clase para las distintas opciones de la carta (ej. serveDishOfTheDay, serveCombinationPlate, serveVermouth y serveBloodyMary, 4) más la localización del empleado encargado de preparar cada comanda (generateRandomEmployeeId, 1).

    • Su resultado es de orden lineal, O(n).

GranularidadWrongCommand

GranularidadWrongCommand2

Intención
intencionCommand
  • Si la responsabilidad de camarero incluye únicamente la colocación de la comanda en un lugar acordado y la posterior recogida de la comanda preparada para servírsela al cliente, y otros, cocineros y bármanes, se responsabilizan por separado de escoger la comanda que van a preparar según sus condiciones y dejarla preparada en el lugar acordado, habrá dos equipos de trabajo independientes y mucho menos trabajo para el camarero.

  • Cohesión

    • La cohesión de la clase Waiter es adecuada puesto que asume una única responsabilidad, centrada en la gestión de las comandas de cara al cliente, colaborando con el servicio del cocinero/barman.

  • Acoplamiento

    • El acoplamiento de la clase Waiter (ej. 1) depende del tipo de la comanda (ej. Order, 1).

    • Su resultado es de orden constante, O(1).

AcoplamientoFineCommand

AcoplamientoFineCommand2

  • Granularidad

    • La granularidad de la clase Waiter (ej. 1) depende del tipo de la comanda.

    • Se calcula como la suma de los métodos relativos a la responsabilidad de la clase (ej. serve, 1).

    • Su resultado es de orden constante, O(1).

GranularidadFineCommand

GranularidadFineCommand2

Intención Aplicabilidad
  • La intención de este patrón es encapsular una petición como un objeto, permitiendo de ese modo parametrizar clientes con diferentes peticiones, colas o solicitudes registradas, y apoyar las operaciones de deshacer.

  • Parametrizar objetos con una acción a realizar. Los objetos Command son un sustituto orientado a objetos para las funciones callback.

  • Especificar, poner en cola y ejecutar peticiones en diferentes instantes del tiempo. Un objeto Command puede tener un tiempo de vida independiente de la petición original. Si se puede representar el receptor de una petición en una forma independiente del espacio de direcciones, entonces se puede transferir un objeto Command con la petición a un proceso diferente y llevar a cabo la petición allí, desacoplando la fuente de una petición del objeto que la cumple.

  • Permitir deshacer. La operación execute de Command puede guardar en el propio comando el estado que anule sus efectos. Debe añadirse a la interfaz Command una operación undo que anule los efectos de una llamada anterior a execute. Los comandos ejecutados se guardan en una lista que hace las veces de historial. Se pueden lograr niveles ilimitados de deshacer y repetir recorriendo dicha lista hacia atrás y hacia adelante llamando respectivamente a las operaciones undo y execute.

  • Permitir registrar los cambios de manera que se puedan volver a aplicar en caso de caída del sistema. Aumentando la interfaz de Command con operaciones para cargar y guardar se puede mantener un registro persistente de los cambios. Recuperarse de una caída implica volver a cargar desde el disco los comandos guardados y volver a ejecutarlas con la operación execute.

  • Estructurar un sistema alrededor de operaciones de alto nivel construidas sobre operaciones básicas. Esta estructura es común en los sistemas de información que permiten transacciones, que encapsulan un conjunto de cambios sobre unos datos. El patrón Command ofrece un modo de modelar transacciones. Los comandos tienen una interfaz común, permitiendo así invocar a todas las transacciones del mismo modo. El patrón también facilita extender el sistema con nuevas transacciones.

Solución

Participantes

Estructura General

  • Command: declara una interfaz para ejecutar una operación.

  • ConcreteCommand: clase derivada de Command que define un enlace a un objeto Receiver, y que implementa el método para ejecutar una operación de la interfaz Command invocando la correspondiente operación u operaciones de Receiver.

  • Director: clase que crea un objeto ConcreteCommand y establece su Receiver.

  • Invoker: clase que solicita a través de la interfaz Command que se ejecute la operación.

  • Receiver: clase que sabe cómo llevar a cabo las operaciones asociadas a una petición.

estructuraGeneralCommand

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
  • Desacopla el objeto que invoca la operación de aquél que tiene el conocimiento para ejecutarla.

  • Permite reemplazar objetos Command y Receiver en tiempo de ejecución.

  • Los comandos son objetos de primera clase. Pueden ser manipulados y extendidos como cualquier otro objeto.

  • Es fácil añadir nuevos comandos, ya que no hay que cambiar las clases existentes.

  • Se pueden ensamblar comandos en un comando compuesto para ejecutar una secuencia de comandos.

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
motivacionInterpreter
  • Un operador se responsabiliza de manejar un aparato complejo siguiendo una serie de protocolos, protocolo de mañana o de tarde o …, los cuales constan de diversas combinaciones de diversos tipos de operaciones simples y/o compuestas, que debe saber ejecutar manualmente.

  • Esto complica la comprensión de su comportamiento y tiende a crecer a medida que surgen nuevos protocolos.

  • Cohesión

    • La cohesión de la clase Operator no es adecuada puesto que asume varias responsabilidades dispares o que no le atañen, como el conocimiento y la ejecución manual de los diversos tipos de operaciones simples y/o compuestas.

  • Acoplamiento

    • El acoplamiento de la clase Operator (ej. 1) depende del tipo de la máquina (ej. Machine, 1).

    • Su resultado es de orden constante, O(1).

AcoplamintoWrongInterpreter

AcoplamintoWrongInterpreter2

  • Granularidad

    • La granularidad de la clase Operator (ej. 8) depende de la cantidad de métodos requerido.

    • Se calcula como la suma de los métodos relativos a la responsabilidad de la clase para los distintos tipos de protocolos (ej. performMorningProtocol y performEveningProtocol, 2) más el número de tipos de operaciones (simples y/o compuestas) de las que constan (ej. performCompScriptForStarting, performIterScriptForTasksDE, performIterScriptForTaskF, performIterScriptForTasksFED, performCompScriptForSwitchingOff y performOperation, 6).

    • Su resultado es de orden lineal, O(n).

GranularidadWrongInterpreter

GranularidadWrongInterpreter2

Intención
itencionInterpreter
  • Si la responsabilidad de operador incluye únicamente la solicitud de ejecución automática del protocolo (protocolo de mañana, protocolo de tarde, …) y otros, scripts, se responsabilizan por separado de cómo se interpretan los protocolos en operaciones simples y/o compuestas, ninguna clase será compleja y se evitará la tendencia a crecer.

  • Cohesión

    • La cohesión de la clase Operator es adecuada puesto que asume una única responsabilidad, centrada en la ejecución manual de los diversos tipos de scripts.

  • Acoplamiento

    • El acoplamiento de la clase Operator (ej. 2) depende del tipo de la máquina (ej. Machine, 1) y del tipo del script (ej. Script, 1).

    • Su resultado es de orden constante, O(1).

AcoplamientoFineInterpreter

AcoplamientoFineInterpreter2

  • Granularidad

    • La granularidad de la clase Operator (ej. 1) depende del script.

    • Se calcula como la suma de los métodos relativos a la responsabilidad de la clase (ej. performProtocol, 1).

    • Su resultado es de orden constante, O(1).

GranularidadFineInterpreter

GranularidadFineInterpreter2

Intención Aplicabilidad
  • La intención de este patrón es, dado un lenguaje, definir una representación de su gramática junto con un intérprete que utiliza la representación para interpretar sentencias del lenguaje.

  • Hay un lenguaje que interpretar y se pueden representar las sentencias del lenguaje como árboles sintácticos abstractos.

  • Hay un tipo de problemas que ocurren con cierta frecuencia, y es posible expresar las apariciones de esos problemas como instrucciones de un lenguaje simple, para el cual se puede construir un intérprete que sea capaz de resolver estos problemas mediante la interpretación de dichas instrucciones.

  • El patrón Interpreter funciona mejor cuando:

    • La gramática es simple. Para gramáticas complejas, la jerarquía de clases de la gramática se vuelve grande e inmanejable. Herramientas como los generadores de analizadores sintácticos constituyen una alternativa mejor en estos casos, ya que pueden interpretar expresiones sin necesidad de construir árboles sintácticos abstractos, lo que puede ahorrar espacio y, posiblemente, tiempo.

    • La eficiencia no es una preocupación crítica. Los intérpretes más eficientes normalmente no se implementan interpretando árboles de análisis sintácticos directamente, sino que primero los traducen a algún otro formato. Por ejemplo, las expresiones regulares suelen transformarse en máquinas de estados. Pero incluso en ese caso, el traductor puede implementarse con el patrón Interpreter, de modo que éste sigue siendo aplicable.

Solución

Estructura General

estructuraGeneralInterpreter

Participantes

  • AbstractExpression: declara la interfaz para interpretar, que es común a todos los nodos del árbol sintáctico abstracto.

  • TerminalExpression: clase derivada de AbstractExpression que implementa la interfaz para interpretar asociada con los símbolos terminales de la gramática.

    • Se necesita una instancia de esta clase para cada símbolo terminal de una expresión.

  • NonTerminalExpression: clase derivada de AbstractExpression que define el comportamiento de los objetos asociados con los símbolos no terminales de la gramática, es decir, para las reglas de la gramática, que pueden estar formadas a su vez por otras reglas o por expresiones terminales.

    • Se necesita una instancia de esta clase para cada regla de la gramática.

    • Mantiene variables de instancia de tipo AbstractExpression para cada uno de los símbolos que contiene la regla que representa.

    • Implementa la interfaz para interpretar asociada con los símbolos no terminales de la gramática. Normalmente, esta implementación consiste en llamarse a sí misma recursivamente sobre las variables de instancia que representa.

  • Context: clase que contiene la información que es global al intérprete.

  • Director: clase que construye o recibe un árbol sintáctico abstracto que representa una determinada sentencia del lenguaje definido por la gramática, y que invoca a la operación de interpretar con el contexto adecuado.

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
  • El uso de clases para representar las reglas de la gramática facilita realizar cambios o extensiones de ésta mediante herencia. Esto permite modificar incrementalmente las expresiones existentes y definir otras nuevas como variaciones de las antiguas.

  • Permite incluir nuevos métodos en la interfaz Expression para añadir nuevas maneras de interpretar expresiones aumentando la funcionalidad. Para conseguir mayor flexibilidad sin afectar las clases de la gramática se recomienda considerar la utilización del patrón Visitor.

  • Las gramáticas complejas con muchas reglas son difíciles de mantener, ya que el patrón Interpreter define al menos una clase para cada regla de la gramática (las reglas que se hayan definido usando BNF3 pueden necesitar varias clases). Cuando se da esta circunstancia, se pueden aplicar otros patrones de diseño para mitigar el problema, pero se recomienda considerar el uso de otras técnicas más adecuadas como los generadores de analizadores sintácticos o de compiladores.

    • 3 La notación de Backus-Naur, también conocida por sus denominaciones inglesas Backus-Naur form (BNF), Backus-Naur formalism o Backus normal form, es un metalenguaje usado para expresar gramáticas libres de contexto.

Problemas de Diseño y Rediseño

Problemas de Diseño Problemas de Rediseño

Iterator

Problema

Motivación
motivacionIterator
  • Un cliente se responsabiliza de comprar muebles para una estancia, dormitorio o jardín …, en distintas tiendas de muebles, las cuales tienen distintos criterios de organización de sus plantas, por estancia de la casa o por tamaño de los muebles o … Para ello, el cliente debe averiguar cuál es el criterio de organización y ser capaz de localizar los muebles para la estancia que le interesa.

  • Esto complica la comprensión de su comportamiento y tiende a crecer a medida que surgen nuevos criterios de organización.

  • Cohesión

    • La cohesión de la clase Customer no es adecuada puesto que asume varias responsabilidades dispares o que no le atañen, como el conocimiento del criterio de organización de la tienda y la localización de los muebles en las distintas plantas.

  • Acoplamiento

    • El acoplamiento de la clase Customer (ej. 3) depende de del tipo de tienda de muebles y de sus características.

    • Se calcula como la suma de la tienda (ej. FurnitureStore, 1) más el tipo de plantas de las que consta (ej. Floor, 1) y el tipo de productos que se venden en ella (ej. Furniture, 1).

    • Su resultado es de orden constante, O(1).

AcoplamientoWrongIterator

AcoplamientoWrongIterator2

  • Granularidad

    • La granularidad de la clase Customer (ej. 7) depende de la cantidad de métodos requeridos.

    • Se calcula como la suma de los métodos relativos a la responsabilidad de la clase para comprar muebles según el criterio de organización de cada tienda (ej. buyFurnitureIntoRoomOrganizedFurnitStore y buyFurnitureIntoSizeOrganizedFurnitStore, 2) más el número de tipos de operaciones para localizar los muebles (ej. discoverFurnitStoreFloors, nextProdInRoomOrganizStore, hasMoreProdsInRoomOrganizStore, nextProdInSizeOrganizStore y hasMoreProdsInSizeOrganizStore, 5).

    • Su resultado es de orden lineal, O(n).

GranularidadWrongIterator

GranularidadWrongIterator2

Intención
intencionIterator
  • Si la responsabilidad de cliente incluye únicamente la solicitud de muebles para un tipo de estancia y otros, dependientes, se responsabilizan por separado de conocer el criterio de organización de la tienda y de localizar los muebles, ninguna clase será compleja y se evitará la tendencia a crecer.

  • Cohesión

    • La cohesión de la clase Customer es adecuada puesto que asume una única responsabilidad, centrada en comprar muebles, colaborando con el servicio del dependiente que conoce el criterio de organización de la tienda y la localización de los muebles en las distintas plantas.

  • Acoplamiento

    • El acoplamiento de la clase Customer (ej. 2) depende del tipo de la tienda de muebles (ej. FurnitureStore, 1) y del tipo de dependiente (ej. ShopAssistant, 1).

    • Su resultado es de orden constante, O(1).

AcoplamientoFineIterator

AcoplamientoFineIterator2

  • Granularidad

    • La granularidad de la clase Customer (ej. 1) depende de la cantidad de métodos requeridos.

    • Se calcula como la suma de los métodos relativos a la responsabilidad de la clase (ej. buyRoomFurniture, 1).

    • Su resultado es de orden constante, O(1).

GranularidadFineIterator

GranularidadFineIterator2

Intención Aplicabilidad
  • La intención de este patrón es proveer una forma de acceder secuencialmente a los elementos de un objeto agregado sin exponer su representación subyacente.

  • Acceder al contenido de un objeto agregado, tal como una lista, sin exponer su representación interna.

  • Permitir varias maneras de recorrer los elementos de los objetos agregados:

    • Pudiendo coexistir varios recorridos en espera sobre el mismo objeto agregado, llevados a cabo por el mismo o distintos clientes.

    • Sin afectar la interfaz del objeto agregado con las operaciones para llevar a cabo los diversos recorridos, incluso si se pudiera anticipar los que se necesitan.

  • Proporcionar una interfaz uniforme para recorrer diferentes estructuras agregadas, es decir, para permitir la iteración polimórfica.

Solución

Estructura General

estructuraGeneralIterator

Participantes

  • Iterator: declara la interfaz para recorrer los elementos y acceder a ellos.

    • Como mínimo, define los métodos para la navegación, recuperación y validación: first, next, isDone y currentItem.

    • En agregados ordenados podría añadir previous para posicionar el iterador en el elemento anterior; y

    • En colecciones ordenadas o indexables aplicando algún criterio, skipTo para posicionar el iterador en un objeto que cumpla el criterio especificado.

  • ConcreteIterator: clase derivada de Iterator que implementa la interfaz y mantiene la posición actual en el recorrido del ConcreteAggregate.

    • Normalmente las instancias son creadas por ConcreteAggregate. De hecho, debido al alto acoplamiento con dicha clase, la clase ConcreteIterator es a menudo una clase interna de ConcreteAggregate.

  • Aggregate: declara la interfaz para crear un objeto Iterator.

  • ConcreteAggregate: clase derivada de Aggregate que implementa la interfaz para devolver una instancia del ConcreteIterator apropiado.

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
  • Permite variaciones en el recorrido de un agregado, es decir, agregados complejos pueden ser recorridos de múltiples maneras definiendo nuevas subclases de Iterator.

  • Permite tener varios recorridos simultáneos sobre un agregado, ya que cada iterador mantiene su propio estado del recorrido.

  • Los Iteradores simplifican la interfaz del agregado ya que la interfaz de recorrido del Iterador obvia la necesidad de una interfaz similar en el agregado.

  • La posibilidad de definir una interfaz uniforme para recorrer distintos tipos de agregados simplifica mucho el uso de colecciones y permite utilizar el polimorfismo cuando se trabaja con ellas.

  • Pueden crear una ilusión de estructuras ordenadas. Si un conjunto no soporta la ordenación, su iterador podría proporcionar los elementos en una secuencia arbitraria que podría cambiar con el tiempo. Si no se tiene en cuenta este hecho, se podría estar presuponiendo una coherencia en la estructura subyacente que podría provocar problemas en los clientes.

  • Los iteradores polimórficos pueden dar lugar a agujeros de memoria (memory leaks) en lenguajes como C++, en los que los clientes son responsables de liberar la memoria asignada a un iterador cuando se ha terminado de usarlo. Esto es propenso a errores, especialmente cuando en una operación hay múltiples puntos de salida (ej. si se lanza una excepción, el objeto iterador podría no liberarse nunca).

Problemas de Diseño y Rediseño

Problemas de Diseño Problemas de Rediseño

Dependencias algorítmicas

Acoplamiento

Mediator

Problema

Motivación
motivaionMediator
  • En un encuentro deportivo hay dos equipos con sus correspondientes participantes (jugadores, capitanes, entrenadores, …) que tienen que ser capaces de [red]responsabilizarse de sus funciones individuales, y también de coordinarse a la hora de tomar decisiones sobre el reparto inicial de campos, o si es procedente pitar una tarjeta amarilla o roja o un penalti, o llevar a cabo un cambio de jugadores o solicitar al fisioterapeuta que preste sus servicios en el campo o …, [red]con un criterio de permisividad* más o menos severo a la hora de decidir.

  • Esto complica la comprensión de su comportamiento y tiende a crecer a medida que surgen nuevos tipos de participantes que tendrán que intervenir en la coordinación.

  • Cohesión

    • La cohesión de las especializaciones derivadas de la clase TeamMember para los distintos participantes del partido (Player, Captain, Coach, …) no es adecuada puesto que asumen varias responsabilidades dispares o que no les atañen, como la coordinación y la toma de decisiones sobre el partido.

  • Acoplamiento

    • El acoplamiento de las especializaciones derivadas de la clase TeamMember para los distintos tipos de participante del partido (Player, Captain y Coach) depende de la cantidad de distintos tipos de participantes en el partido (ej. 3).

    • Se calcula como la suma de la cantidad de distintos tipos de participantes en el partido (ej. Player, Captain y Coach, 3), con la simplificación de que todos los participantes se conocen entre sí.

    • Su resultado es de orden lineal, O(n).

AcoplamientoWrongMediator

AcoplamientoWrongMediator2

  • Granularidad

    • La granularidad de las especializaciones derivadas de la clase TeamMember para los distintos tipos de participante del partido (Player, Captain y Coach) depende de la cantidad de métodos requeridos, sean éstos para cumplir con las responsabilidades individuales de la clase o para las tareas de coordinación en la toma de decisiones. Dicha cantidad varía para cada tipo de participante (ej. Player, 4 + 5 = 9; Captain, 2 + 7 = 9; Coach, 1 + 7 = 8), por lo que se considerará para el análisis la media de todos los métodos de las clases que representan a los distintos tipos de participante (ej. 8,6).

    • Se calcula como la media de los métodos relativos a la responsabilidad de cada clase, sean éstos para cumplir con las responsabilidades individuales de la clase o para las tareas de coordinación en la toma de decisiones; es decir, la suma de los métodos relativos a la responsabilidad de todas las clases que representan un tipo de participante (ej. 7 + 19 = 26) dividido entre el número de tipos de participante (ej. 3).

    • Su resultado es de orden lineal, O(n ).

GranularidadWrongMediator

GranularidadWrongMediator2

Intención
itencionMediator
  • En un encuentro deportivo, la solución más habitual es nombrar un árbitro para coordinar y tomar decisiones sobre el partido, con un criterio de permisividad más o menos severo, y de esta manera conseguir que cada tipo de participante se centre en sus responsabilidades individuales evitando la necesidad de que todos los participantes tengan que interactuar entre ellos para coordinarse en la toma de decisiones.

  • Cohesión

    • La cohesión de las especializaciones derivadas de la clase TeamMember para los distintos participantes del partido (Player, Captain, Coach, …) es adecuada puesto que cada una asume una única responsabilidad, centrada en sus funciones propias, colaborando con el servicio de árbitro que se ocupa de la coordinación y la toma de decisiones sobre el partido

  • Acoplamiento

    • El acoplamiento de las especializaciones derivadas de la clase TeamMember para los distintos tipos de participante del partido (Player, Captain y Coach) depende del tipo de árbitro (ej. Referee, 1).

    • Su resultado es de orden constante, O(1).

AcoplamientoFineMediator

AcoplamientoFineMediator2

  • Granularidad

    • La granularidad de las especializaciones derivadas de la clase TeamMember para los distintos tipos de participante del partido (Player, Captain y Coach) depende de la cantidad de métodos requeridos para cumplir con las responsabilidades individuales de la clase. Dicha cantidad varía para cada tipo de participante (ej. Player, 4; Captain, 2; Coach, 1), por lo que se considerará para el análisis la media de todos los métodos de las clases que representan a los distintos tipos de participante (ej. 2,3).

    • Se calcula como la media de los métodos relativos a la responsabilidad de cada clase, es decir, la suma de los métodos relativos a la responsabilidad de todas las clases que representan un tipo de participante (ej. 7) dividido entre el número de tipos de participante (ej. 3).

    • Su resultado es de orden lineal, O(n ).

GranularidadFineMediator

GranularidadFineMediator2

Intención Aplicabilidad
  • La intención de este patrón es encapsular en un único objeto la manera en la que interactúan un conjunto de objetos. De esta manera se consigue promover un menor acoplamiento al evitar a los objetos referirse explícitamente unos a otros, así como permitir variar su interacción de forma independiente.

  • Simplificar la comunicación entre un conjunto de objetos que se comunican con reglas bien definidas, pero de manera compleja. Es decir, objetos cuyas interdependencias no están estructuradas y son difíciles de entender.

  • Permitir reutilizar objetos evitando que éstos se refieran a otros muchos objetos con los que se comunican.

  • Permitir que un comportamiento que está distribuido entre varias clases pueda ser particularizado sin necesidad de muchas subclasificaciones.

Solución

Participantes

Estructura General

  • Mediator: declara la interfaz para comunicarse con sus objetos Colleague.

    • Permite que los colegas trabajen con diferentes subclases de mediador y viceversa.

  • ConcreteMediator: clase derivada de Mediator que define el comportamiento cooperativo coordinando objetos Colleague.

  • Colleague: declara la interfaz para comunicarse con el objeto Mediator.

  • ConcreteColleague: clase derivada de Colleague que define el comportamiento para comunicarse con el objeto Mediator. Cada Colleague se comunica con su Mediator cada vez que, de no existir éste, se hubiera comunicado con otro Colleague.

estructuraGenralMediator

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
  • Localiza el comportamiento que de otro modo estaría distribuido entre varios objetos. El cambio de este comportamiento requiere únicamente subclases de mediador, pudiendo ser reutilizadas las clases colegas tal cual, reduciéndose así la necesidad de subclasificación.

    • Esto permite enfocarse en cómo interactúan los objetos de un sistema más allá de su comportamiento individual y simplificar el mantenimiento de la estrategia general de comunicación puesto que ésta es responsabilidad exclusiva del mediador.

  • Promueve el desacoplamiento entre colegas. Se pueden variar y reutilizar las clases colega y mediador de forma independiente.

    • El desacoplamiento entre colegas evita que los distintos objetos no puedan trabajar sin el soporte de los demás, lo cual podría convertir el sistema en un monolito difícil de mantener.

  • Simplifica los protocolos de comunicación entre los objetos, puesto que sustituye las interacciones muchos-a-muchos entre los objetos (en el peor de los casos, cada objeto interaccionaría con el resto de los objetos del conjunto), por interacciones uno-a-muchos entre el mediador y sus colegas. Las relaciones uno-a-muchos son más fáciles de entender, mantener y extender.

  • La encapsulación de protocolos y la centralización de comportamiento específico de la aplicación pueden convertir al objeto mediador en un objeto más complejo que cualquier colega individual y hacer de él un monolito difícil de mantener.

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
motivacionMemento
  • Una persona se responsabiliza de crear planes que constan de distintos elementos, título, fecha, participantes, detalles, coordinador, secretario…, y de realizar modificaciones sobre los mismos. Para poder recuperar una versión anterior del plan, se responsabiliza también de proporcionar a un archivo aquellos elementos del plan que pueden ser modificados y no se pueden deducir a partir de otros (título, participantes y detalles) para que éste guarde una copia de los mismos y así poder consultarlos posteriormente.

  • Esto complica la comprensión de su comportamiento y tiende a crecer a medida que surgen nuevos elementos del plan que pueden ser modificados y no se pueden deducir a partir de otros.

  • Cohesión

    • La cohesión de la clase Person no es adecuada puesto que asume varias responsabilidades dispares o que no le atañen, como proporcionar al archivo aquellos elementos del plan que pueden ser modificados y no se pueden deducir a partir de otros, violando la encapsulación.

  • Acoplamiento

    • El acoplamiento de la clase Person (ej. 2) depende del tipo del plan (ej. Plan, 1) y del tipo del archivo en el que se almacena (ej. PlanArchive, 1).

    • Su resultado es de orden constante, O(1).

AcoplamientoWrongMemento

AcoplamientoWrongMemento2

  • Granularidad

    • La granularidad de los métodos de la clase Person (ej. 3) depende de los elementos del plan que pueden ser modificados y no se pueden deducir a partir de otros.

    • Se calcula como la suma de los elementos del plan que pueden ser modificados y no se pueden deducir a partir de otros (ej. título, participantes y detalles, 3).

    • Su resultado es de orden lineal, O(n).

GranularidadWrongMemento

GranularidadWrongMemento2

Intención
intencionMemento
  • Si la responsabilidad de persona incluye únicamente la creación y modificación de planes, y otros, planes, se responsabilizan por separado de proporcionar a un archivo una copia de aquellos elementos del plan que pueden ser modificados y no se pueden deducir a partir de otros (título, participantes y detalles), ninguna clase será compleja y se evitará la tendencia a crecer, y se preservará la encapsulación del plan.

  • Cohesión

    • La cohesión de la clase Person es adecuada puesto que asume una única responsabilidad, centrada en la gestión de planes, colaborando con el servicio del plan para proporcionar al archivo aquellos elementos del plan que pueden ser modificados y no se pueden deducir a partir de otros. preservando la encapsulación.

  • Acoplamiento

    • El acoplamiento de la clase Person (ej. 2) depende del tipo del plan (ej. Plan, 1) y del tipo del archivo en el que se almacena (ej. PlanArchive, 1).

    • Su resultado es de orden constante, O(1).

AcoplamientoFineMemento

AcoplamientoFineMemento2

  • Granularidad

    • La granularidad de los métodos de la clase Person (ej. 1) depende del plan.

    • Su resultado es de orden constante, O(1).

GranularidadFineMemento

GranularidadFineMemento2

Intención Aplicabilidad
  • La intención de este patrón es, sin violar la encapsulación, capturar y externalizar el estado interno de un objeto de tal forma que el objeto puede ser restaurado a este estado después.

  • Aplicable cuando se cumplen las dos siguientes condiciones:

    • Se necesita guardar una instantánea del estado interno de un objeto (o de parte de éste) para poder restaurar el objeto a dicho estado posteriormente. Tiene gran utilidad cuando se implementan mecanismos de deshacer que permiten a los usuarios anular operaciones provisionales y/o recuperarse de errores.

    • Se quiere evitar utilizar una interfaz directa para obtener el estado del objeto que pudiera violar el principio de encapsulación por exponer detalles de implementación internos.

Solución

Participantes

Estructura General

  • Originator: clase que crea un objeto Memento, el cual contiene una instantánea de su estado interno actual, y que puede utilizar posteriormente dicho Memento para volver a ese estado.

  • Memento: clase que almacena el estado interno del objeto Originator. Tiene dos interfaces:

    • Una interfaz amplia, que permite al Originator acceder a todos los datos necesarios para volver a su estado anterior.

    • Una interfaz reducida, que sólo permite pasar el objeto Memento a otros objetos, protegiendo así el Memento frente a accesos que no provengan del Originator. Solo el Originator puede almacenar y recuperar información del Memento – el Memento es “opaco” a otros objetos.

  • Caretaker: clase responsable de guardar en lugar seguro los objetos Memento, así como de borrar los Memento que custodia. Nunca examina sus contenidos ni opera sobre ellos, es decir, sólo conoce la interfaz reducida del Memento.

estructuraGeneralMemento

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
  • El Memento evita exponer información que sólo debería ser manejada por el Originator, pero que sin embargo debe ser guardada fuera de éste. El patrón preserva la encapsulación, lo cual permite:

    • Simplificar el Originator, ya que se evita que éste tenga que responsabilizarse de registrar y almacenar todos los estados distintos solicitados por los clientes para preservar la encapsulación, ocupándose únicamente de crear los objetos Memento y de saber utilizarlos cuando los clientes se lo soliciten.

    • Simplificar los clientes, ya que estos no necesitan saber nada sobre el funcionamiento interno del Originator, que podría ser potencialmente complejo, excepto cómo obtener un Memento y cómo utilizarlo, y evita que los clientes tengan que notificar a los objetos Originator cuando han acabado.

    • No comprometer la fiabilidad y extensibilidad de la aplicación exponiendo interioridades de los objetos innecesariamente.

  • El uso del patrón Memento podría suponer un coste considerable si el Originator debe copiar grandes cantidades de información para guardarlas en el Memento o si los clientes crean y devuelven Mementos a su Originator con mucha frecuencia. A menos que encapsular y restablecer el estado sea poco costoso, el patrón podría no ser apropiado.

    • Una de las principales clases afectadas por este coste es Caretaker ya que, aunque desconoce cuánto estado hay en un Memento, es la responsable de gestionar el ciclo de vida de estos objetos tras su creación.

  • En algunos lenguajes puede ser difícil garantizar que sólo el Originator acceda al estado del Memento.

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
motivacionObserver
  • 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 tiene que requerir ayuda a una serie de distintas personas de su entorno, madre o pareja o jefe o…, para sobrellevar este valle de lágrimas.

  • Esto complica la comprensión de su comportamiento y tiende a crecer a medida que surgen nuevos tipos de personas en el entorno a los que solicitar ayuda.

  • Cohesión

    • La cohesión de la clase Person no es adecuada puesto que asume varias responsabilidades dispares o que no le atañen, como la gestión de los distintos tipos personas que le prestan ayuda según las circunstancias.

  • Acoplamiento

    • El acoplamiento de la clase Person (ej. 3) depende del número de tipos de personas de su entorno encargados de prestar ayuda.

    • Se calcula como la suma del número de tipos de personas de su entorno encargados de prestar ayuda (ej. Mother, Partner y Boss, 3).

    • Su resultado es de orden lineal, O(n).

AcoplamientoWrongObserver

AcoplamientoWrongObserver2

  • Granularidad

    • La granularidad de la clase Person (ej. 22) depende de la cantidad de métodos requeridos.

    • Se calcula como la suma de los métodos relativos a la responsabilidad de la clase para gestionar los distintos tipos de problemas en la vida (ej. isInTrouble, setInTrouble, getTroubleReason, loseALovedOne, haveAFlue, wasteAllSavings, …, 12) más el producto del número de tipos de personas de su entorno (ej. Mother, Partner y Boss, 3) por los métodos relativos para gestionar la existencia de cada tipo de persona (ej. attach, detach e is, 3), más el método para realizar la notificación (ej. notifyKnowPeople, 1).

    • Su resultado es de orden lineal, O(n).

GranularidadWrongObserver

GranularidadWrongObserver2

Intención
itencionObserver

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.

  • Cohesión

    • La cohesión de la clase Person es adecuada puesto que asume una única responsabilidad, centrada en la gestión de su vida, notificando que requiere ayuda a la red de seguridad de personas de su entorno.

  • Acoplamiento

    • El acoplamiento de la clase Person (ej. 1) depende de un tipo de persona conocida (ej. KnownPeople, 1).

    • Su resultado es de orden constante, O(1).

AcoplamientoFineObserver

AcoplamientoFineObserver2

  • Granularidad

    • La granularidad de la clase Person (ej. 15) depende de la cantidad de métodos requeridos.

    • Se calcula como la suma de los métodos relativos a la responsabilidad de la clase para gestionar los distintos tipos de problemas en la vida (ej. isInTrouble, setInTrouble, getTroubleReason, loseALovedOne, haveAFlue, wasteAllSavings, …, 12) más los métodos relativos para gestionar la existencia de un tipo de persona conocida (ej. attach y detach, 2), más el método para realizar la notificación (ej. notifyKnowPeople, 1).

    • Su resultado es de orden lineal, O(n).

GranularidadFineObserver

GranularidadFineObserver2

Intención Aplicabilidad
  • La intención de este patrón es definir una dependencia uno-a-muchos entre objetos de tal forma que cuando un objeto cambia su estado, todos los objetos dependientes son notificados y actualizados automáticamente.

  • Abstracciones que tienen dos aspectos, uno de los cuales depende del otro. La encapsulación de estos aspectos en objetos separados permite modificarlos y reutilizarlos de manera independiente.

  • Un cambio sobre un objeto requiere cambiar otros y no se conoce cuántos objetos necesitan ser cambiados.

  • Un objeto debería ser capaz de notificar a otros objetos sin hacer asunciones sobre quiénes son estos objetos. En otras palabras, no se quiere estos objetos altamente acoplados, para permitir una mayor reusabilidad.

Solución

Estructura General

estructuraGeneralObserver

Participantes

  • Subject: declara la interfaz para registrar y desregistrar objetos Observer, así como para realizar la notificación a los mismos cuando se produce en él algún cambio relevante.

    • El sujeto observado puede tener cualquier número de observadores dependientes. Todos los observadores que se hayan suscrito previamente son notificados si el observado sufre un cambio de estado.

    • Se envían notificaciones sin tener conocimiento de quienes son sus observadores.

  • ConcreteSubject: clase derivada de Subject que implementa la interfaz:

    • Almacena el estado de interés para los objetos ConcreteObserver.

    • Envía una notificación a sus observadores cuando cambia su estado.

    • Necesita mantener una colección de Observer para efectuar las notificaciones.

  • Observer: declara la interfaz para actualizar los objetos que deben ser notificados ante cambios en un Subject.

    • En respuesta a la notificación recibida, cada observador consultará al sujeto observado para sincronizar su estado con el estado de éste.

  • ConcreteObserver: clase derivada de Observer que implementa la interfaz:

    • Mantiene una referencia a un ConcreteSubject.

    • Guarda un estado que debería ser consistente con el del sujeto.

    • Implementa los métodos para responder a las notificaciones recibidas, garantizando la correcta actualización de su estado, es decir, su actualización de manera consistente con el estado del sujeto al que observa.

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
  • Permite variar independientemente observados y observadores. Se pueden reutilizar los sujetos observados sin reutilizar sus observadores y viceversa. Esto posibilita añadir observadores sin modificar los sujetos observados u otros observadores.

  • Todo sujeto observado conoce que tiene una lista de observadores, conforme a la sencilla interfaz de la clase abstracta Observer, pero el sujeto no conoce la clase concreta de ninguno de sus observadores.

    • El acoplamiento entre sujetos observados y observadores es abstracto y mínimo, pudiendo éstos pertenecer incluso a capas diferentes de abstracción del sistema.

    • Si se agrupasen el sujeto y el observador en un único objeto, el objeto resultante debería abarcar dos capas (violando la arquitectura de capas) o ser forzado a residir en una capa u otra (lo que podría comprometer las abstracciones de las capas).

  • La notificación que envía el observado no necesita especificar sus receptores. La notificación es multidifundida (broadcast) automáticamente a todos los objetos que estén suscritos. El observado no se preocupa de cuántos objetos interesados existen; su responsabilidad solo es notificar a sus observadores. Esto da la libertad de añadir y borrar observadores en cualquier momento. El observador ya verá si maneja o ignora la notificación

  • Se pueden producir actualizaciones inesperadas, debidas a que, como los observadores no tienen conocimiento de la presencia de otros, no pueden ver el coste final de modificar el sujeto observado.

    • Una operación aparentemente inocua sobre el sujeto puede provocar una cascada de cambios en los observadores y sus objetos dependientes.

    • Por otra parte, si existen criterios de dependencia que no estén bien definidos o mantenidos, podrían dar lugar a cambios espurios, que pueden ser difíciles de localizar. Este problema se ve agravado por la utilización de un protocolo simple de actualización que no proporcione ningún detalle sobre lo que ha cambiado en el sujeto observado. Sin un protocolo adicional para ayudar a los observadores a descubrir estos cambios, la tarea de deducirlos puede no ser en absoluto trivial.

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
  • Una persona realiza diversas actividades a lo largo del día, comer o bailar o trabajar o …, pero dependiendo de su estado de ánimo, feliz o triste o …, las realizará con distintos algoritmos.

  • Esto complica la comprensión de su comportamiento y tiende a crecer a medida que surgen nuevos estados de ánimo.

motivacionState
  • Cohesión

    • La cohesión de la clase Person no es adecuada puesto que asume varias responsabilidades dispares o que no le atañen, como la definición de distintos algoritmos para cada actividad.

  • Acoplamiento

    • El acoplamiento de la clase Person (ej. 0) es nulo.

    • Su resultado es de orden constante, O(1).

AcoplamientoWrongState

AcoplamientoWrongState2

  • Granularidad

    • La granularidad de la clase Person (ej. 9) depende de la cantidad de métodos requeridos.

    • Se calcula como la suma de los métodos relativos a la responsabilidad de la clase (ej. eat, dance y work, 3) más el producto del número de responsabilidades de la clase por el número de estados de ánimo (ej. feliz y triste, 2, por eat, dance y work, 3).

    • Su resultado es de orden cuadrático, O(n2).

GranularidadWrongState

GranularidadWrongState2

Intención
  • La responsabilidad de la persona incluye la delegación de la realización de la actividad en diferentes estados de ánimo, que se responsabilizan por separado del algoritmo a seguir para realizar cada actividad dependiendo del estado de ánimo. De esta manera, ninguna clase será compleja y se evitará la tendencia a crecer.

itencionState
  • Cohesión

    • La cohesión de la clase Person es adecuada puesto que asume una única responsabilidad, centrada en la delegación, colaborando con el servicio del estado de ánimo y sus algoritmos específicos.

  • Acoplamiento

    • El acoplamiento de la clase Person (ej. 1) depende del tipo del estado de ánimo (ej. Mood, 1).

    • Su resultado es de orden constante, O(1).

AcoplamientoFineState

AcoplamientoFineState2

  • Granularidad

    • La granularidad de la clase Person (ej. 3) depende de la cantidad de métodos requeridos.

    • Se calcula como la suma de los métodos relativos a la responsabilidad de la clase (ej. eat, dance y work, 3).

    • Su resultado es de orden lineal, O(n).

GranularidadFineState

GranularidadFineState2

Intención Aplicabilidad
  • La intención de este patrón es permitir a un objeto alterar su comportamiento cuando su estado interno cambia pareciendo que el objeto cambia su clase.

  • El comportamiento de un objeto depende de su estado, y debe cambiar en tiempo de ejecución dependiendo de ese estado.

  • Para definir los estados internos se usan valores de datos (a menudo una o más constantes enumeradas) y las operaciones consultan dichos estados explícitamente, dando lugar a largas sentencias condicionales con múltiples ramas que dependen del estado del objeto. Muchas veces son varias las operaciones que contienen esta misma estructura condicional, por lo que cuando se necesita añadir un nuevo estado podría necesitarse cambiar varias operaciones, complicando el mantenimiento.

    • El patrón State pone cada rama de la condición en una clase aparte, lo cual permite tratar al estado del objeto como un objeto de pleno derecho que puede variar de manera independiente de otros objetos.

Solución

Participantes Estructura General
  • Context: declara una interfaz de interés para los clientes y mantiene una instancia de una subclase ConcreteState que define su estado actual.

    • Delega todas las llamadas a los métodos específicos del estado actual.

  • State: declara una interfaz para encapsular el comportamiento asociado con un determinado estado del objeto Context.

  • ConcreteState: clase derivada de State que implementa un comportamiento específico asociado con un estado de Context.

estructuraGeneralState

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
  • Sitúa en un objeto todo el comportamiento asociado con un determinado estado. Como todo el código dependiente del estado reside en una subclase de State, pueden añadirse fácilmente nuevos estados y transiciones definiendo nuevas subclases.

    • Esto incrementa el número de clases y es menos compacto que una única clase, pero se evita que la lógica que determina las transiciones entre estados resida en grandes sentencias if o switch monolíticas, siendo en su lugar repartida entre las subclases de State.

    • Al encapsular cada transición y acción en una clase, estamos elevando la idea de un estado de ejecución a objetos de estado en toda regla. Esto impone una estructura al código y hace que su intención sea más clara.

  • Introducir objetos separados para los diferentes estados hace que las transiciones sean más explícitas. Además, los objetos State pueden proteger a Context frente a estados internos inconsistentes, ya que las transiciones entre estados son atómicas para él puesto que tienen lugar cambiando una única variable (la variable de tipo State del objeto Context).

    • Si en lugar de utilizar objetos State, un objeto define su estado actual únicamente en términos de valores de datos internos, sus transiciones entre estados carecen de una representación explícita; solo aparecen como asignaciones a determinadas variables.

  • El patrón aumenta el número de objetos de una aplicación.

    • A veces se puede reducir este coste si los objetos State no tienen variables, es decir, si el estado que representan está totalmente representado por su tipo, pudiendo así varios contextos compartir un mismo objeto State. Cuando se comparten los estados de este modo, son en esencia objetos FlyWeight que no tienen estado intrínseco, sino solo comportamiento.

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
motivacionStrategy
  • Una persona realiza diversas actividades a lo largo de su vida, flirtear o hacer amistades o …, pero tras realizar un análisis del contexto, las realizará con distintas estrategias que implementarán el algoritmo correspondiente.

  • Esto complica la comprensión de su comportamiento y tiende a crecer a medida que surgen nuevas estrategias.

  • Cohesión

    • La cohesión de la clase Person no es adecuada puesto que asume varias responsabilidades dispares o que no le atañen, como la definición de distintos algoritmos para cada actividad.

  • Acoplamiento

    • El acoplamiento de la clase Person (ej. 0) es nulo.

    • Su resultado es de orden constante, O(1).

AcoplamientoWrongStrategy

AcoplamientoWrongStrategy2

  • Granularidad

    • La granularidad de la clase Person (ej. 6) depende de la cantidad de métodos requeridos.

    • Se calcula como la suma de los métodos relativos a la responsabilidad de la clase (ej. flirt y makeFriendship, 2) más el número de estrategias de aproximación (ej. amigo común, hobby común e invitación directa, 3) más el método para analizar el contexto y establecer la estrategia (ej. setApproach, 1).

    • Su resultado es de orden lineal, O(n).

GranularidadWrongStrategy

GranularidadWrongStrategy2

Intención
  • Si la responsabilidad de persona incluye únicamente la delegación de la realización de la actividad y otros, consejero sentimental y estrategias de aproximación, se responsabilizan por separado del análisis del contexto y del algoritmo a seguir para realizar cada actividad, ninguna clase será compleja y se evitará la tendencia a crecer.

itencionStrategy
  • Cohesión

    • La cohesión de la clase Person es adecuada puesto que asume una única responsabilidad, centrada en la delegación, colaborando con el servicio de la estrategia de aproximación y sus algoritmos específicos.

  • Acoplamiento

    • El acoplamiento de la clase Person (ej. 1) depende del tipo de la estrategia de aproximación (ej. Approach, 1).

    • Su resultado es de orden constante, O(1).

AcoplamientoFineStrategy

AcoplamientoFineStrategy2

  • Granularidad

    • La granularidad de la clase Person (ej. 3) depende de la cantidad de métodos requeridos.

    • Se calcula como la suma de los métodos relativos a la responsabilidad de la clase (ej. flirt y makeFriendship, 2) más el método para recibir la estrategia a aplicar (ej. set, 1).

    • Su resultado es de orden lineal, O(n).

GranularidadFineStrategy

GranularidadFineStrategy2

Intención Aplicabilidad
  • La intención de este patrón es definir una familia de algoritmos, encapsulando cada uno y haciéndolos intercambiables, permitiendo al algoritmo variar de manera independiente del cliente que lo utilice.

  • Muchas clases relacionadas difieren en su comportamiento. Las estrategias permiten configurar una clase con un determinado comportamiento de entre muchos posibles en tiempo de ejecución.

  • Se necesitan distintas variantes de un algoritmo, por ejemplo, con distintas soluciones de compromiso entre tiempo y espacio. Pueden usarse estrategias cuando estas variantes se implementan como una jerarquía de clases de algoritmos.

  • Un algoritmo usa datos que los clientes no deberían conocer. El patrón Strategy permite evitar exponer estructuras de datos complejas y dependientes del algoritmo.

  • Una clase define muchos comportamientos, y éstos se representan como múltiples sentencias condicionales en sus operaciones.

Solución

Participantes Estructura General
  • Strategy: declara una interfaz común a todos los algoritmos permitidos. Context utiliza esta interfaz para llamar al algoritmo definido por una ConcreteStrategy.

  • ConcreteStrategy: clase derivada de Strategy que implementa un algoritmo específico.

  • Context: clase que utiliza las diferentes estrategias para una determinada tarea. Se configura con un objeto ConcreteStrategy y mantiene una referencia al objeto Strategy que está utilizando en cada momento.

    • Puede definir una interfaz que permita a una ConcreteStrategy acceder a sus datos.

estructuraGeneralStrategy

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
  • Las jerarquías de clases Strategy definen una familia de algoritmos o comportamientos para ser reutilizados por los contextos. La herencia puede ayudar a sacar factor común de la funcionalidad de estos algoritmos.

    • La herencia ofrece otra forma de permitir una variedad de algoritmos o comportamientos. Se puede heredar de Context para proporcionar diferentes comportamientos, pero esto liga el comportamiento a Context, mezclando la implementación del algoritmo con la de Context. Esto dificulta su comprensión, mantenimiento y extensión e impide la modificación del algoritmo dinámicamente. El resultado será una gran cantidad de clases relacionadas cuya única diferencia es el algoritmo o comportamiento que utilizan. Encapsular el algoritmo en clases Strategy separadas permite variar el algoritmo independientemente de su contexto, haciendo este más fácil de cambiar, comprender y extender.

  • El patrón ofrece una alternativa a las sentencias condicionales para seleccionar el comportamiento deseado, ya que, en su ausencia, cuando se juntan muchos comportamientos en una clase es difícil no recurrir a éstas para seleccionar el comportamiento correcto.

  • Las estrategias permiten proporcionar distintas implementaciones del mismo comportamiento. El cliente puede elegir entre estrategias con diferentes soluciones de compromiso entre tiempo y espacio.

  • El patrón tiene el inconveniente potencial de que un cliente debe comprender cómo difieren las distintas ConcreteStrategy antes de seleccionar la adecuada. Los clientes pueden estar expuestos a cuestiones de implantación. Por tanto, debería usarse solo cuando la variación de comportamiento sea relevante a los clientes.

  • La interfaz de Strategy es compartida por todas las clases ConcreteStrategy, ya sea el algoritmo que implementan trivial o complejo. Por tanto, es probable que algunos objetos ConcreteStrategy no usen toda la información que reciben a través de dicha interfaz; pudiendo darse el caso incluso de que las estrategias concretas simples no utilicen ninguna en absoluto. Esto implica que habrá ocasiones en las que el contexto cree e inicialice parámetros que nunca se usan. Si esto puede ser un problema, necesitaremos un acoplamiento más fuerte entre Strategy y Context.

  • El patrón aumenta el número de objetos de una aplicación. A veces se puede reducir este coste si los objetos ConcreteStrategy no tienen estado, pudiendo así ser compartidos por el contexto. Esto también es posible si, en caso de necesitar cualquier estado residual, es Context quien se encarga de mantenerlo, pasándoselo a ConcreteStrategy en cada petición. Las estrategias compartidas no deberían mantener el estado entre invocaciones. Cuando se comparten las estrategias de este modo, son en esencia objetos FlyWeight que no tienen estado intrínseco, sino solo comportamiento.

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
motivacionTemplateMethod
  • Un profesor se responsabiliza de enseñar con el mismo algoritmo, presentarse, presentar el curso, explicar los contenidos, responder las dudas, evaluar y despedir el curso, pero dependiendo de la localización, clase o campo o …, algunos pasos del algoritmo serán variables ya que los realizará de manera distinta, bien con distintos recursos didácticos (proyector o pizarra o …, Moodle o folio o …) o con distinto nivel de detalle (exhaustivo o superficial o …) , o …

  • Esto complica la comprensión de su comportamiento y tiende a crecer a medida que surgen nuevas opciones para los pasos variables.

  • Cohesión

    • La cohesión de la clase Professor no es adecuada puesto que asume varias responsabilidades dispares o que no le atañen, como el conocimiento de todos los posibles pasos alternativos del algoritmo dependiendo de la localización.

  • Acoplamiento

    • El acoplamiento de la clase Professor (ej. 0) es nulo.

    • Su resultado es de orden constante, O(1).

AcoplamientoWrongTemplateMetod

AcoplamientoWrongTemplateMetod2

  • Granularidad

    • La granularidad de la clase Professor (ej. 9) depende de la cantidad de métodos requeridos.

    • Se calcula como la suma de los métodos relativos a la responsabilidad de la clase (ej. teach, 1) más los métodos relativos a los pasos comunes del algoritmo (ej. introduceYourself, introduceCourse, answerDoubts y concludeCourse, 4) más el producto de las localizaciones por los métodos relativos a los pasos alternativos del algoritmo (ej. clase y campo, 2, por explainSubject y assess, 2).

    • Su resultado es de orden cuadrático, O(n2).

GranularidadWrongTemplateMethod

GranularidadWrongTemplateMethod2

Intención
  • Si la responsabilidad de profesor incluye únicamente la solicitud de realizar los pasos variables y otros, profesores concretos, se responsabilizan por separado de cómo se realizan los pasos variables, ninguna clase será compleja y se evitará la tendencia a crecer.

itencionTemplateMethod
  • Cohesión

    • La cohesión de la clase Professor es adecuada puesto que asume una única responsabilidad, centrada en los pasos comunes del algoritmo, colaborando con el servicio de profesor concreto que se responsabiliza de los pasos alternativos del algoritmo.

  • Acoplamiento

    • El acoplamiento de la clase Professor (ej. 0) es nulo.

    • Su resultado es de orden constante, O(1).

AcoplamientoFineTemplateMethod

AcoplamientoFineTemplateMethod2

  • Granularidad

    • La granularidad de la clase Professor (ej. 7) depende de la cantidad de métodos requeridos.

    • Se calcula como la suma de los métodos relativos a la responsabilidad de la clase (ej. teach, 1) más los métodos relativos a los pasos comunes del algoritmo (ej. introduceYourself, introduceCourse, answerDoubts y concludeCourse, 4) más los métodos relativos a los pasos variables del algoritmo (ej. explainSubject y assess, 2).

    • Su resultado es de orden lineal, O(n ).

GranulaidadFineTemplateMethod

GranulaidadFineTemplateMethod2

Intención Aplicabilidad
  • La intención de este patrón es definir el esqueleto de un algoritmo en una operación, difiriendo algunos pasos a las subclases para que éstas puedan redefinirlos sin cambiar la estructura.

  • Implementar las partes invariantes de un algoritmo, permitiendo que sean las subclases quienes implementen el comportamiento que puede variar.

  • Factorizar un comportamiento repetido de varias subclases para ubicarlo en una clase común evitando así código duplicado.

  • Controlar la extensión de subclases en puntos específicos mediante la definición de un método plantilla que llame a operaciones vacías (conocidas como operaciones de enganche), que puedan ser redefinidas por las subclases, restringiendo así las extensiones de las mismas a estos puntos de enganche.

Solución

Estructura General

structuraGeneralTemplateMethod

Participantes

  • AbstractClass: declara una clase abstracta o interfaz que:

    • Define las operaciones primitivas abstractas que son implementadas por las subclases para realizar los pasos de un algoritmo.

      • Se pueden identificar las operaciones que deberían ser redefinidas añadiendo un sufijo a su nombre, por ejemplo “do-”.

      • Se pueden declarar con visibilidad protegida para garantizar que solo puedan ser llamadas por el método plantilla.

      • Se debe minimizar el número de operaciones primitivas que una subclase debe redefinir para dar cuerpo al algoritmo.

    • Implementa un método plantilla que define el esqueleto de un algoritmo. Este método llama a las operaciones primitivas abstractas, así como a operaciones definidas en AbstractClass u otras clases.

      • Se puede declarar como final para evitar que pueda ser redefinido.

  • ConcreteClass: clase derivada de AbstractClass que implementa las operaciones primitivas abstractas para realizar los pasos del algoritmo específicos de las subclases.

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
  • Los métodos plantilla son una técnica fundamental de reutilización de código.

  • Los métodos plantilla llevan a una estructura de control invertido, es decir, una clase padre llama a las operaciones de una subclase y no al revés. Los métodos plantilla llaman a los siguientes tipos de operaciones primitivas:

    • Operaciones abstractas que deben ser obligatoriamente redefinidas por las subclases.

    • Operaciones concretas, ya sea de la ConcreteClass, de la AbstractClass, o de las clases cliente.

    • Operaciones de enganche, que ofrecen un comportamiento predeterminado, pero que pueden ser opcionalmente redefinidas por las subclases si es necesario.

    • Métodos de fabricación (ver patrón Factory Method).

  • Un número elevado de operaciones primitivas que requieran ser redefinidas puede resultar tedioso para los clientes. Un objetivo importante para diseñar métodos plantilla es minimizar el número de operaciones primitivas que una subclase debe redefinir para dar cuerpo al algoritmo.

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
  • Un paciente cuando tiene un accidente se responsabiliza de realizar distintas actividades médicas, evaluar los daños, diagnosticarse, operarse, … sobre las distintas partes del cuerpo, cabeza, tronco, extremidad, …

  • Esto complica la comprensión de su comportamiento y tiende a crecer a medida que surgen nuevas partes del cuerpo a tratar.

motivacionVisitor
  • Cohesión

    • La cohesión de la clase Patient no es adecuada puesto que asume varias responsabilidades dispares o que no le atañen, como el conocimiento de todas las posibles actividades médicas a realizar sobre las distintas partes del cuerpo.

  • Acoplamiento

    • El acoplamiento de la clase Patient (ej. 1) depende del tipo de partes del cuerpo (ej. BodyPart, 1).

    • Su resultado es de orden constante, O(1).

AcoplamientoWrongVisitor

AcoplamientoWrongVisitor2

  • Granularidad

    • La granularidad de la clase Patient (ej. 4) depende de la cantidad de métodos requeridos.

    • Se calcula como la suma de los métodos relativos a la responsabilidad de la clase (ej. evaluate, diagnose y operate, 3) más el método para añadir las partes del cuerpo (ej. addBodyPart, 1).

    • Su resultado es de orden lineal, O(n).

GranularidadWrongVisitor

GranularidadWrongVisitor2

Intención
  • Si la responsabilidad del paciente incluye únicamente la aceptación de la visita sobre las distintas partes del cuerpo por el personal del hospital y otros, el personal del hospital, se responsabilizan por separado de cómo se realizan las distintas actividades médicas sobre cada parte del cuerpo del paciente, ninguna clase será compleja y se evitará la tendencia a crecer.

itencionVisitor
  • Cohesión

    • La cohesión de la clase Patient es adecuada puesto que asume una única responsabilidad, centrada en aceptar la visita del personal del hospital, colaborando con el servicio de personal que se responsabiliza de realizar las actividades médicas sobre las distintas partes del cuerpo.

  • Acoplamiento

    • El acoplamiento de la clase Patient (ej. 2) depende del tipo de partes del cuerpo (ej. BodyPart, 1) y del tipo de personal del hospital (ej. Staff, 1).

    • Su resultado es de orden constante, O(1).

AcoplamientoFineVisitor

AcoplamientoFineVisitor2

  • Granularidad

    • La granularidad de la clase Patient (ej. 2) depende de la cantidad de métodos requeridos.

    • Se calcula como la suma del método relativo a aceptar las visitas del tipo del personal del hospital (ej. accept, 1) más el método para añadir las partes del cuerpo (ej. addBodyPart, 1).

    • Su resultado es de orden constante, O(1).

GranularidadFineVisitor

GranularidadFineVisitor2

Intención Aplicabilidad
  • La intención de este patrón es representar una operación a realizar sobre los elementos de una estructura de objetos, permitiendo definir nuevas operaciones sin cambiar las clases los de elementos sobre los que opera.

  • Una estructura de objetos contiene muchas clases de objetos con diferentes interfaces y se quiere realizar operaciones sobre esos elementos que dependen de la clase concreta.

  • Se necesita realizar muchas operaciones distintas y no relacionadas sobre objetos de una estructura de objetos y se quiere evitar “contaminar” sus clases con dichas operaciones. El patrón Visitor permite mantener juntas operaciones relacionadas definiéndolas en una clase. Cuando la estructura de objetos es compartida por varias aplicaciones, el patrón Visitor permite poner operaciones sólo en aquellas aplicaciones que las necesitan.

  • Las clases que definen la estructura de objetos rara vez cambian, pero muchas veces se quiere definir nuevas operaciones sobre la estructura. Cambiar las clases de la estructura de objetos requiere redefinir la interfaz para todos los visitantes, lo que es potencialmente costoso. Si las clases de la estructura cambian con frecuencia, probablemente sea mejor definir las operaciones en las propias clases.

Solución

Estructura General

estructuraGeneralVisitor

Participantes

  • Visitor: declara una clase abstracta o interfaz que define una operación visit para cada clase ConcreteElement de la estructura de objetos. El nombre y/o signatura de la operación identifican a la clase que envía la petición visit al visitante. Esto permite al visitante determinar la clase concreta de elemento que está siendo visitada, pudiendo así acceder al elemento directamente a través de su interfaz particular.

  • ConcreteVisitor: clase derivada de Visitor que implementa las operaciones visit definidas en la interfaz. Cada clase ConcreteVisitor representa una operación específica del sistema, y cada operación visit implementa el comportamiento específico del visitante para la clase ConcreteElement correspondiente. ConcreteVisitor proporciona el contexto para el algoritmo y guarda su estado local. Muchas veces este estado acumula resultados durante el recorrido de la estructura.

  • Element: declara una clase abstracta o interfaz que define una operación accept que recibe un Visitor como argumento. La clase Element representa los objetos sobre los que actúa Visitor.

  • ConcreteElement: clase derivada de Element que implementa la operación accept definida en la interfaz invocando al método visit apropiado definido en Visitor. Cada clase ConcreteElement representa una entidad específica del sistema.

  • ObjectStructure: clase que consta de una serie de elementos y que:

    • Define una interfaz para enumerar sus elementos.

    • Puede proporcionar una interfaz de alto nivel para permitir al visitante visitar a sus elementos.

    • Puede ser una clase Composite o una colección, como una lista o un conjunto.

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
  • Los visitantes facilitan añadir nuevas operaciones que dependen de los componentes de objetos complejos. Se puede definir una nueva operación sobre una estructura simplemente añadiendo un nuevo visitante. Si, por el contrario, extendiésemos la funcionalidad sobre muchas clases, habría que cambiar cada clase para definir una nueva operación.

  • El comportamiento similar no está desperdigado por las clases que definen la estructura de objetos; sino que está localizado en un visitante. Las partes de comportamiento no relacionadas se dividen en sus propias subclases del visitante. Esto simplifica tanto las clases que definen los elementos como los algoritmos definidos por los visitantes. Cualquier estructura de datos específica de un algoritmo puede estar oculta en el visitante.

  • Los visitantes pueden acumular estado a medida que van visitando cada elemento de la estructura de objetos. Sin un visitante, este estado se pasaría como argumentos extra a las operaciones que realizan el recorrido, o quizá como variables globales.

  • El patrón Visitor hace que sea complicado añadir nuevas subclases ConcreteElement, ya que cada nueva clase ConcreteElement da lugar a una nueva operación abstracta de Visitor y a su correspondiente implementación en cada clase ConcreteVisitor. A veces se puede proporcionar en Visitor una implementación predeterminada que puede ser heredada por la mayoría de los ConcreteVisitor, pero esto representa una excepción más que una regla.

    • Por tanto, la cuestión fundamental a considerar a la hora de aplicar el patrón Visitor es si es más probable que cambie el algoritmo aplicado sobre una estructura de objetos o las clases de los objetos que componen la estructura. La jerarquía de clases Visitor puede ser difícil de mantener cuando se añaden nuevas clases de ConcreteElement con frecuencia.

  • Si se utiliza un iterador para visitar a los objetos de una estructura llamando a sus operaciones a medida que los recorre, como un iterador no puede trabajar en varias estructuras de objetos con distintos elementos, esto implica que todos los elementos que el iterador puede visitar deben tener una clase padre común Element.

    • Se impone en este caso una restricción que el patrón Visitor no tiene, puesto que en el patrón se pueden visitar objetos que no tienen una clase padre común, ya que se puede añadir cualquier tipo de objeto a la interfaz de Visitor.

  • El enfoque del patrón Visitor asume que la interfaz de ConcreteElement es lo bastante potente como para que los visitantes hagan su trabajo. Como resultado, el patrón suele obligarnos a proporcionar operaciones públicas que acceden al estado interno de un elemento, lo que puede comprometer su encapsulación.

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

tablaComparativa

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

  • * cosificación

Relaciones entre Patrones de Diseño

relacionesPatrones2

Aplicación de Patrones de Diseño

seleccion
uso
Aplicaciones

Sintesis

sintesis

Bibliografía

Obra, Autor y Edición Portada Obra, Autor y Edición Portada
  • Patrones de diseño

    • Erich Gamma et al

    • Anaya (2002)

height32

  • AntiPatterns: Refactoring Software, Architectures, and Projects in Crisis

    • William J. Brown

    • John Wiley & Sons (1998)

height32

Ponente

  • Luis Fernández Muñoz

setillo

  • Doctor en Inteligencia Artificial por la UPM

  • Ingeniero en Informática por la UMA

  • Diplomado en Informática por la UPM

  • Profesor Titular de ETSISI de la UPM