ClubEnsayos.com - Ensayos de Calidad, Tareas y Monografias
Buscar

Patrones De Diseño


Enviado por   •  9 de Octubre de 2013  •  8.343 Palabras (34 Páginas)  •  270 Visitas

Página 1 de 34

PATRONES DE DISEÑO.

LOS MOCHIS SIN., 10 NOVIEMBRE 2008

Patrones de diseño o más comúnmente conocidos como "Design Patterns". Son soluciones simples y elegantes a problemas específicos y comunes del diseño orientado a objetos. Son soluciones basadas en la experiencia y que se ha demostrado que funcionan. Es evidente que a lo largo de multitud de diseños de aplicaciones hay problemas que se repiten o que son análogos, es decir, que responden a un cierto patrón. Sería deseable tener una colección de dichos patrones con las soluciones más óptimas para cada caso.

Los patrones de diseño no son fáciles de entender, pero una vez entendido su funcionamiento, los diseños serán mucho más flexibles, modulares y reutilizables. Han revolucionado el diseño orientado a objetos y todo buen arquitecto de software debería conocerlos.

El término “patrón de diseño” no es extremadamente preciso pero sí muy útil. En la forma en la que se suele entender actualmente fue acuñado en Gamma y col. (1994), importante referencia que se suele designar abreviadamente como GoF. Por la manera en que se emplea este concepto en esta referencia básica y en la mayoría de usos posteriores, se refiere a una manera especialmente inteligente y perspicaz de resolver un tipo general de problema.

A pesar de constar el término “diseño” no se suele considerar que se refiera únicamente a la fase de diseño de un programa, es una solución completa que incluye análisis, diseño e implementación. Un “patrón de diseño” no se considera bien especificado hasta que se ha podido plasmar en forma de código en algún lenguaje, pero claramente no es una convención o receta “idiomática”, ligada a un lenguaje concreto, como podría la relacionada con “cómo recorrer de forma eficiente un vector en C empleando aritmética de punteros”, por poner un ejemplo. Sin embargo, en GoF se remarca que un patrón puede serlo, tener sentido identificarlo como tal o no, dependiendo del lenguaje utilizado. En el lenguaje de implementación elegido en GoF, C++, un patrón como

“herencia” no tiene sentido (ya está implícito en el propio lenguaje) mientras que sería una solución general muy adecuada a numerosos problemas si el lenguaje de implementación fuese C, por ejemplo. En un ámbito de problemas y de conceptos más cercano a la Estadística, y que trataremos con mayor detalle más adelante, un patrón de diseño muy común como “objeto función” según la terminología de Eckel(2003) o

“comando” según la terminología de GoF, tiene sentido en Java o en C++, pero no en

Python, donde todo, y en concreto cualquier método o función miembro, es un objeto.

Tampoco hay que confundir “patrón de diseño” con un algoritmo adecuado para resolver un tipo concreto de problema –que, lógicamente, se tendría que implementar de forma distinta según el lenguaje. Un patrón de diseño debe ser una especificación muy general, una especie de invariante que se cumple en problemas de ámbitos diversos y que define una solución general y muy estable.

Lista con los patrones de diseño a objetos más habituales publicados en el libro "Design Patterns ", escrito por los que comúnmente se conoce como GoF (gang of four, "pandilla de los cuatro").

Patrones de creación

Abstract Factory.

Proporciona una interfaz para crear familias de objetos o que dependen entre sí, sin especificar sus clases concretas.

La idea detrás del patron Abstract Factory (que en español se traduciría como fabrica abstracta) consiste en la noción de que nuestro programa (o el cliente de una clase que nosotros proporcionamos) trabaja con una serie de productos (como los de una fábrica) que tienen unas determinadas características (por ejemplo tenemos productos embotellados y productos en tetrabrick). Nuestro programa va a utilizar dichos productos realizando una serie de acciones sobre ellos (como meter las botellas en unos camiones y los tetrabricks en otros) sin importarle quien le está suministrando los productos.

Así mismo existen una serie de fábricas que producen esos productos que vamos a tratar, una fábrica fabrica cocacola y otra pepsi pero ambas en botellas de las que nuestro programa trata. Al final, el concepto básico consiste en que a nuestro programa (o cliente) no le importa lo que haya dentro de la botella ni quien lo haya producido mientras sea una botella.

Desde el punto de vista software el ejemplo anterior se traduce en una situación en la que nuestro programa maneja un tipo de objetos con unas características comunes y algunas caracterísiticas propias. Esto en general en software se resuelve mediante el uso de dos características de los lenguajes de programación orientados a objetos, las clases abstractas y los interfaces.

Problema

El patrón de diseño Abstract Factory aborda el problema de la creación de familias de objetos (como por ejemplo iterfaces gráficos) que comparten toda una serie de características comunes en los objetos que componen dichas familias.

Aplicabilidad

El uso de este patrón está recomendado para situaciones en las que tenemos una familia de productos concretos y prevemos la inclusión de distintas familias de productos en un futuro.

Estructura

De esta forma nuestro programa realiza unas ciertas operaciones sobre dichas características comunes sin importarle que otras características tenga el objeto en cuestión. Por otro lado existen distintos productores de dichos objetos. Un ejemplo muy típico en muchos frameworks de programación que sigue este patrón se da en el caso de las familias de interfaces gráficos. Así existen diversas fábricas de interfaces que proporcionan sus propios botones, campos de texto, listas desplegables, etc... Todas ellas basadas en los tipos básicos. Los clientes obtienen una familia y proceden a utilizar los controles usando el tipo abstracto genérico que da soporte.

Ejemplos reales

Familia de comunicaciones

Un caso bastante común es el similar al presentado en el ejemplo de código, es decir, una familia de algoritmos de comunicación por distintos medios que permiten el envio de información entre pares (por ejemplo). De esta forma nuestro programa puede usar comunicación TCP, UDP o cualquier otro protocolo que se nos ocurra sobre un dispositivo no estandar o que no soporte IP.

Interfaces gráficos

Otro caso relativamente común de uso de este patrón se da en la creación de familias de interfaces gráficos en las cuales los elementos (productos) del interfaz se mantienen constantes (por ejemplo labels, botones, cajas de texto ...) pero el dibujado de dichos elementos puede delegarse en distintas familias (por ejemplo QT, GTK, etc) de forma que, en función de la fábrica seleccionada obtenemos unos botones u otros.

Builder.

Separa la construcción de un objeto complejo de su representación, de forma que el mismo proceso de construcción pueda crear diferentes representaciones.

Permite a un objeto (fuente) construir un objeto complejo especificando sólo su tipo. El objeto constructor (fuente) se compone de una serie de partes que individualmente van formando el objeto complejo. Así se abstrae el proceso de creación del objeto complejo para que se puedan crear representaciones diferentes con el mismo proceso.

Se nos pueden ocurrir muchos ejemplos para el patrón Builder, vehículos, recetas, electrodomésticos y cualquier cosa que esté compuesta por muchas partes. También se utiliza mucho para crear los objetos del patrón Composite (patrón estructural). El siguiente ejemplo va ensamblando los componentes de un ordenador según el uso que le queramos dar.

El patrón constructor sigue la misma idea que el patrón factoría, pero no se encarga simplemente de devolver una clase, si no de construir una composición de objetos entera, por compleja o sencilla que sea. El caso más habitual es el de construir una interfaz de usuario, pero no se limita únicamente a componentes visuales.

Intención

Separar la construcción de un objeto complejo de su representación de modo que el mismo proceso de construcción pueda crear diferentes representaciones.

Motivación

Los objetos que dependen de un algoritmo tendrán que cambiar cuando el algoritmo cambia. Por lo tanto los algoritmos que estén expuestos a dicho cambio deberían ser separados, permitiendo de esta manera reutilizar algoritmos para crear diferentes representaciones.

El patrón Builder se usa cuando:

• El algoritmo para creación de un objeto complejo debe ser independiente de las partes que conforman el objeto y cómo están ensambladas.

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

Estructura

Consecuencias

1. Permite variar la representación interna de un producto.

2. Permite separar el código de la construcción y la representación.

3. Da control refinado sobre el proceso de construcción.

Factory Method.

Define una interfaz para crear un objeto, pero deja que sean las subclases quienes decidan qué clase instanciar. Permite que una clase delegue en sus subclases la creación de objetos.

Es una simplificación del Abstract Factory, en la que la clase abstracta tiene métodos concretos que usan algunos de los abstractos; según usemos una u otra hija de esta clase abstracta, tendremos uno u otro comportamiento.

Aplicabilidad

 Una clase no puede anticipar la clase de objeto que debe crear.

 Una clase quiere que sus subclases especifiquen los objetos a crear.

 Hay clases que delegan responsabilidades en una o varias subclases.

Ventajas e inconvenientes

• Elimina la necesidad de introducir clases específicas en el código del creador. Solo maneja la interfaz Product, por lo que permite añadir cualquier clase ConcretProduct definida por el usuario.

• Tener que crear una subclase de Creator en los casos en los que esta no fuera necesaria de no aplicar el patrón.

Prototype.

Especifica los tipos de objetos a crear por medio de una instancia prototípica, y crear nuevos objetos copiando este prototipo. Tiene como finalidad crear nuevos objetos duplicándolos, clonando una instancia creada previamente.

Este patrón es motivo donde en ciertos escenarios es preciso abstraer la lógica que decide que tipos de objetos utilizará una aplicación, de la lógica que luego usarán esos objetos en su ejecución. Los motivos de esta separación pueden ser variados, por ejemplo, puede ser que la aplicación deba basarse en alguna configuración o parámetro en tiempo de ejecución para decidir el tipo de objetos que se debe crear.

Este patrón propone la creación de distintas variantes del objeto que nuestra aplicación necesite, en el momento y contexto adecuado. Toda la lógica necesaria para la decisión sobre el tipo de objetos que usará la aplicación en su ejecución debería localizarse aquí. Luego, el código que utiliza estos objetos solicitará una copia del objeto que necesite. En este contexto, una copia significa otra instancia del objeto. El único requisito que debe cumplir este objeto es suministrar la prestación de clonarse. Cada uno de los objetos prototipo debe implementar el método Clone().

Permite realizar agrupaciones de órdenes de forma similar a una macro.

Hay quien lo utiliza para implementar la copia de la orden al histórico de órdenes.

Singleton.

Garantiza que una clase sólo tenga una instancia, y proporciona un punto de acceso global a ella.

Es la manera en la cual podemos restringir la creación de una clase a uno o solo algunos objetos. Esto quiere decir que es una manera en la cual se va a diseñar una clase para que solo se pueda crear una instancia de la misma o dicho de otro modo es la manera en la cual una clase solo puede crear un objeto. Su intención consiste en garantizar que una clase sólo tenga una instancia y proporcionar un punto de acceso global a ella.

Si tenemos una clase coche que representa uno de estos coches modernos, controlado totalmente por una computadora. Este coche esta compuesto de muchos elementos, como por ejemplo el motor, que se compone a su vez de otros, hasta que llegamos a un nivel dado, por ejemplo al séptimo. ¿Que sucede si un elemento de ese nivel, por ejemplo un termostato en las válvulas tiene que dar un mensaje a la clase coche?, como por ejemplo para decirle que muestre un indicador en el cuadro de mandos avisando de una temperatura elevada. Podríamos ir pasando la referencia al coche de un nivel a otro, pero eso se hace impracticable, por lo que a veces es conveniente una visibilidad global (aunque pueda implicar un acoplamiento de paquetes, por ejemplo), usando para ello el patrón Singleton, que se asegura de que se producirá una sola instancia de coche y de que esta podrá ser recuperada en cualquier momento por un método de clase.

Otro caso hipotético de aplicación podría ser: tenemos una clase BaseDeDatos que nos devuelve un objeto llamado "bd" una vez creada la conexión con la base de datos. En vez de permitir que las aplicaciones usen libremente la clase y puedan crear tantas instancias del objeto "Base de Datos" como aplicaciones y accesos a bases existan, se decide restringir la creación a una sola instancia y esta será compartida y usada por todos.

¿Cómo funciona el patrón de diseño Singleton?

La forma de proceder del patrón es la siguiente: si se solicita una instancia del objeto:

a) si no existe (o sea, la primera vez que se usa) se crea la instancia.

b) si existe una instancia (es la segunda o más vez que se usa), devuelvo la existente, todas las veces que se me solicite.

c) el constructor de la clase debe permanecer "anulado" definiéndolo como "privado". De esta forma se asegura que no se puedan crear instancias de forma directa.

Las situaciones más habituales de aplicación de este patrón son aquellas en las que dicha clase controla el acceso a un recurso físico único (como puede ser el ratón o un archivo abierto en modo exclusivo) o cuando cierto tipo de datos debe estar disponible para todos los demás objetos de la aplicación.

Ventajas

Se tiene controlada la creación de objetos y podremos además disminuir el uso de memoria al tener una sola instancia que se usa en todo el contexto de la aplicación.

Patrones estructurales

Adapter.

Convierte la interfaz de una clase en otra distinta que es la que esperan los clientes. Permiten que cooperen clases que de otra manera no podrían por tener interfaces incompatibles.

El patrón Adapter (Adaptador) se utiliza para transformar una interfaz en otra, de tal modo que una clase que no pudiera utilizar la primera, haga uso de ella a través de la segunda.

Convierte la interface de una clase en otra interface que el cliente espera. Adapter permite a las clases trabajar juntas, lo que de otra manera no podrían hacerlo debido a sus interfaces incompatibles.

¿Qué es un adaptador y cómo lo utilizo?

El adaptador, como implica su nombre, describe una solución al problema de tener dos interfaces disparejas actuando como un mecanismo de traslación entre ellas.

¿Cómo yo reconozco dónde necesito un adaptador?

La definición formal del adaptador, dada en "Design Patterns, Elements of Reusable Object-Oriented Software" por Erich Gamma, Richard Helm, Ralph Johnson y John Vlissides es:

Convertir la interfaz de una clase en otra interfaz de cliente esperada.

La necesidad de un adaptador generalmente resulta a partir de las modificaciones o mejoramientos de las bibliotecas de clases o componentes COM, Por ejemplo, re-factorizar frecuentemente puede provocar cambios significativos en la forma en que necesita ser definida la interfaz para una clase. Esto a su vez, puede provocar más cambios en el código de la aplicación que llama al servicio de su clase. Hacer los cambios es siempre una opción de alto riesgo que debe ser evitada siempre que sea posible.

Cuando el código fuente está disponible, una solución es mantener los métodos en la interfaz original a la clase; pero eliminar la implementación en los métodos nuevos. Los métodos viejos no hacen más el trabajo real, en su lugar, ellos "controlan" las llamadas a otros métodos. Sin embargo, esto requiere aún, que se modifique el código existente.

La alternativa es crear una clase nueva la cual replique la interfaz esperada y traduzca las llamadas a la interfaz en el formato correcto para reemplazarlo. Al tratar con componentes en los cuales el código no está disponible, es el único método para tratar con los cambios en su interfaz.

¿Cuáles son los componentes de un adaptador?

Un adaptador tiene dos componentes esenciales. Primero, necesitamos una clase que defina la interfaz que tiene que ser adaptada, el "adaptado". Segundo, necesitamos la clase "adaptador" que defina la interfaz esperada y haga corresponder los métodos de la interfaz con aquellos definidos por el adaptador. El cliente llama uno de los métodos "esperados" que están expuesto en un lado del adaptador, el que simplemente pasa la llamada al método apropiado en el adaptado.

¿Cómo implemento un adaptador?

En otras palabras, un adaptador es típicamente implementado utilizando herencia múltiple, al crear una subclase del adaptador que herede los métodos privados y, permitiendo que herede además, la interfaz pública de las clases que definen la interfaz requerida.

Esto presenta problemas en Visual FoxPro, porque no soporta múltiple herencia, que es necesaria para este caso. Una nueva característica que fue introducida en Visual FoxPro 7.0 es la palabra clave IMPLEMENTS que permite que una clase Visual FoxPro, definida por código, herede una interfaz que está definida en un tipo de biblioteca. Sin embargo, debido a que depende del tipo de biblioteca, puede ser utilizada solamente con objetos COM, y no con clases nativas de Visual FoxPro, por lo que realmente no es de mucha ayuda en este caso.

La mejor forma de implementar un adaptador en Visual FoxPro es, crear una clase que exponga los métodos de la interfaz esperada, y que guarde una referencia de objeto al adaptado. Los objetos cliente pueden llamar a sus métodos esperados que a su vez llaman los métodos apropiados del adaptado. El resultado es, que en Visual FoxPro hay una pequeña diferencia entre el adaptador y el decorador, excepto en su objetivo. Un decorador modifica su comportamiento, mientras un adaptador simplemente modifica una interfaz.

Resumen del patrón de adaptador

El patrón adaptador se utiliza para evitar la necesidad de cambiar el código cuando cambia una interfaz, o para permitir futuras modificaciones o implementaciones cuando diseña clases genéricas. Sin embargo, debido a que Visual FoxPro no admite herencia múltiple, el único mecanismo de implementación disponible es idéntico al que utiliza el Decorador.

Bridge.

Desvincula una abstracción de su implementación, de manera que ambas puedan variar de forma independiente.

Frecuentemente referenciado como la "madre de los patrones de diseño", porque es el patrón de diseño más básico y usted va a descubrir que es más familiar con los patrones en general, cuando encuentre el puente (de alguna forma) como base de casi todos los otros patrones.

La definición formal de un puente, dada en "Design Patterns, Elements of Reusable Object-Oriented Software" por Erich Gamma, Richard Helm, Ralph Johnson y John Vlissides es que un Puente es:

"Desacoplar una abstracción de su implementación de tal forma que las dos puedan variar independientemente."

¿Cuáles son los componentes de un puente?

Un puente tiene dos componentes esenciales, la "abstracción", que es el objeto responsable de inicializar una operación, y la "implementación", que es el objeto que la lleva a cabo. La abstracción conoce su implementación porque guarda una referencia a la misma, o porque lo posee (por ejemplo, lo contiene). Observe que frecuentemente ocurre que la abstracción crea la implementación, que no es un requerimiento absoluto del patrón. Un puente también podría hacer uso de una referencia ya existente para una misma implementación de objeto. De hecho, diseñar puentes de esta forma, puede ser muy eficiente porque diferentes objetos abstractos pueden utilizar el mismo objeto implementación. Debido a que Visual FoxPro tiene un modelo de contenedores muy bueno, la mayoría de los desarrolladores notarán que lo han utilizado (no intencionalmente) para implementar Puentes con más frecuencia de lo que imaginan. La mayoría de los objetos "administradores" ( por ejemplo: El Administrador de Formularios, El Administrador de archivos, el Administrador de mensajes), son en realidad ejemplos de patrón de Puente.

Sin embargo, no confunda el envío de mensajes con el puente. Si un método de un botón de comandos llama a un método en su formulario padre, esto implementa directamente la acción necesaria, que es enviar un mensaje, no es un puente. Sin embargo, si el método del formulario va a llamar a otro objeto para que lleve a cabo la acción, entonces tenemos un puente.

Es interesante, otra posibilidad que ocurre cuando un formulario (u otro contenedor) tiene una propiedad que guarda una referencia a un objeto de implementación. Un objeto contenedor (por ejemplo un botón de comandos en un formulario) puede acceder a esta propiedad directamente y tomar una referencia local al objeto de implementación. Puede entonces, llamar a los métodos del objeto de implementación directamente. Ahora, ¿este es un puente, o un envío de mensajes? De hecho, probablemente podría argumentar ambos lados del caso con igual justificación. Sin embargo, la respuesta en realidad no importa, este asunto surge sólo porque Visual FoxPro expone las propiedades como "Públicas" de forma predeterminada, y también implementa punteros hacia atrás, los que permiten a los objetos dirigirse a sus padres directamente. En la mayoría de los entornos orientados a objeto, es sencillamente imposible para los objetos comportarse tan toscamente entre ellos.

Entonces, para implementar un puente necesita identificar qué objeto va a realizar la función de abstracción y cuál la implementación. Una vez que lo haya definido, todo lo que queda por decidir es cómo el puente entre los dos será codificado y esto dependerá enteramente del escenario.

Composite.

Combina objetos en estructuras de árbol para representar jerarquías de parte-todo. Permite que los clientes traten de manera uniforme a los objetos individuales y a los compuestos.

Permite construir objetos complejos por medio de la composición recursiva de objetos similares. También permite que estos objetos sean tratados de manera uniforme, si hacer distinciones entre los objetos simples y los complejos. El patrón composite permite construir objetos complejos componiendo de forma recursiva objetos similares en una estructura de árbol. Permite manipular todos los objetos contenidos en el árbol de forma uniforme, ya que todos ellos poseen una interfaz común definida en la clase.

El patrón Composite se puede utilizar cuando:

• Se busca representar una jerarquía de objetos como “parte-todo”.

• Se busca que el cliente puede ignorar la diferencia entre objetos primitivos y compuestos (para que pueda tratarlos de la misma manera).

En conclusión este patrón tiene como propósito de representar estructuras parte-todo, donde los clientes pueden tratar de la misma forma a los objetos compuesto y individuos ó primitivos.

Conclusiones

• Sirve para representar jerarquías parte-todo.

• Se quiere encapsular la diferencia entre objetos simples composiciones de objetos.

• Permite tratar de manera homogénea a objetos simples y compuestos.

• Simplifica el código de los clientes, evitando distinciones de casos.

• Facilita la definición de nuevos tipos de componentes sin afectar a los clientes.

• Puede obligar a realizar un diseño demasiado generalista que dificulte la definición de compuestos donde se restrinja el tipo de componentes.

Decorator.

Añade dinámicamente nuevas responsabilidades a un objeto, proporcionando una alternativa flexible a la herencia para extender la funcionalidad.

El patrón Decorator es una alternativa para la herencia cuando se identifica que va a ocurrir una "explosión" de subclases (creo que en inglés es "inheritance explotion") (lo que muchas veces ocurre cuando se necesita añadir comportamiento en tiempo de ejecución...).

Decorator es un patrón que se utiliza para incrementar un conjunto de propiedades de un objeto dinámicamente y sin tener que agregar estas propiedades a la clase base.

El decorador describe una solución al problema de agregar una funcionalidad a un objeto sin cambiar realmente nada del código en el objeto.

¿Cómo reconozco cuándo necesito un decorador?

La definición formal del decorador, dada en "Design Patterns, Elements of Reusable Object-Oriented Software" por Erich Gamma, Richard Helm, Ralph Johnson y John Vlissides es:

Adjuntar responsabilidad adicional a un objeto dinámicamente.

La necesidad de cambiar dinámicamente la funcionalidad o comportamiento de un objeto surge típicamente en una de estas dos situaciones. Primero, cuando el código fuente del objeto no está disponible, puede ser que el objeto sea un control ActiveX o una biblioteca de clases de terceras partes que estemos utilizando. Segundo, cuando la clase es ampliamente utilizada; pero la responsabilidad específica, se necesita sólo en una o más situaciones particulares y pueda ser inapropiado agregar el código a la clase.

El patrón decorador nos permite solucionar el mismo problema sin necesidad de crear subclases. En lugar de definir un nuevo objeto, que tiene exactamente la misma interfaz como el cálculo de la tasa, pero que incluye además el código necesario para determinar la tasa apropiada para una localización dada. El objeto "mira" justo como el cálculo de la tasa como su cliente; pero, porque también guarda una referencia al objeto de cálculo de tasa, puede "pre-procesar" cualquier requerimiento de un índice y luego, simplemente puede dar la implementación real cuando esté lista.

¿Cuáles son los componentes del decorador?

Un decorador, tiene dos requerimientos esenciales. Primero, necesitamos la clase implementación que define la interfaz y el núcleo de funcionalidad. Segundo, necesitamos un decorador que reproduzca la interfaz de implementación, y guarde una referencia a el. El objeto cliente ahora dirige las llamadas que podrían ir directamente a la implementación, en su lugar, del objeto decorador. La estructura básica del patrón decorador es:

Este patrón es en realidad un puente extendido. Debido a que el cliente está afectado, puede dirigir el objeto decorador como si realmente fuera la implementación al final del puente estándar, porque la interfaz de los dos es la misma. Debido a que la implementación está afectada, la petición mira exactamente igual que si fuera directamente desde el cliente. En otras palabras, no necesita nunca conocer incluso que el decorador existe.

¿Cómo implementar un decorador?

Una nueva clase, nombrada "cntDecorador" ha sido definida para implementar el ejemplo del decorador. Tiene tres propiedades y un método que se muestran a continuación:

Nombre Descripción

cImpclass Nombre de la clase a instanciar para este decorador.

cImplib Biblioteca de clases para la implementación de la clase a instanciar.

oCalculator Referencia de objeto para instanciar la clase que es el implementador real al método CalcTax(). Este objeto es el instanciado en el Init() del decorador.

CalcTax Implementación del decorador para el método equivalente en el implementador 'real'

La clase decorador es realmente muy simple. Al crearse, instancia una clase implementación real, la cual está definida por sus propiedades: nombre de clase y biblioteca. Además implementa un método operacional (en este caso el método CalcTax()), nombrado de la misma forma y que tiene la misma estructura que el método real a implementar.

Ejemplo

Patrón decorador:

Supone que vas a un hotel, cada noche de hotel en habitación compartida tiene un precio base de 100$.

A mitad de la noche se te ocurre bajar a la piscina y la entrada vale $5.

Luego de la piscina pides comida (en tu habitación), esta comida tiene un precio de $10.

¿Cuanto sería el total? , sería un poco dificil de obtener datos al tener una array o alguna estructura de todos los objetos pues estos objetos son de distintas clases y prbablemente de distintos métodos.

Cual es la solución, pues el patrón decorador, simplemente cada cosa nueva que pides tiene un atributo que es un objeto del precio base que se va actualizando y cuando creas el objeto "adicional" simplemente le vas pasando ese objeto, de esta forma para obtener la cuenta la obtienes desde el último objeto "adicional" creado.

Facade.

Proporciona una interfaz unificada para un conjunto de interfaces de un subsistema. Define una interfaz de alto nivel que hace que el subsistema se más fácil de usar.

Consiste en implementar un interfaz simplificado para un sistema complejo. La idea es implementar una clase con un interfaz sencillo y que encapsule los detalles de la interacción entre todas las clases del sistema. Es importante notar que se siguepermitiendo el acceso directo a las clases del sistema a los clientes que necesiten "acceso a bajo nivel" pero se simplifica la interacción para los que no necesiten más que operacionescomunes.

Hacer una biblioteca de software más fácil de usar y entender, ya que facade implementa métodos convenientes para tareas comunes;

Hacer el código que usa la librería más legible, por la misma razón;

Reducir la dependencia de código externo en los trabajos internos de una librería, ya que la mayoría del código lo usa Facade, permitiendo así más flexibilidad en el desarrollo de sistemas;

Envolver una colección mal diseñada de APIs con un solo API bien diseñado.

Flyweight.

Usa el compartimiento para permitir un gran número de objetos de grano fino de forma eficiente. (Peso ligero): Reduce la redundancia cuando gran cantidad de objetos poseen idéntica información.

Problema que soluciona

Necesitamos representar gráficamente muchas pelotas idénticas que rebotan en los bordes de una ventana, así que creamos una clase que tenga por atributos las coordenadas, el radio y el color con que se dibujará la pelota.

Problema: Aunque las coordenadas son distintas, como queremos que nuestras pelotas sean iguales, el radio y el color se repetirán en cada instancia, desperdiciando memoria.

Implementación

Crear una clase PelotaFlyweight, que contendrá la información común (radio y color) y otra clase PelotaConcreta, que contendrá las coordenadas concretas de cada pelota y una referencia a un objeto de tipo PelotaFlyweight.

Al crearse instancias de PelotaConcreta, se les deberá proveer de referencias a la instancia de PelotaFlyweight adecuada a nuestras necesidades.

En este caso solamente tendríamos una instancia de PelotaFlyweight, puesto que hemos dicho que todas nuestras pelotas tienen el mismo radio y color, pero pensando en un ejemplo en el que tuviéramos varios grupos de pelotas, y dentro de cada uno de los cuales se compartieran el radio y el color, se puede utilizar Flyweight conjuntamente con el patrón Factory, de tal modo que este último, en el momento en que se le soliciten instancias de PelotaConcreta con determinadas características (mismo radio y color que el solicitado), compruebe si ya existe un PelotaFlyweight con ese radio y color, y devuelva esa referencia o, en caso de que no exista, la cree y la registre. El patrón Factory se encargaría de gestionar los PelotaFlyweight existentes.

Proxy.

Proporciona un sustituto o representante de otro objeto para controlar el acceso a éste.

Proxy se utiliza como intermediario para acceder a un objeto, permitiendo controlar el acceso a él.

Problema que soluciona

Necesitamos crear objetos que consumen muchos recursos, pero no queremos instanciarlos a no ser que el cliente lo solicite o se cumplan otras condiciones determinadas.

Implementación

Tenemos un objeto padre Asunto del que heredan otros dos: AsuntoReal y Proxy, todos ellos tienen un método petición(). El cliente llamaría al método petición() de Asunto, el cual pasaría la petición a Proxy, que a su vez instanciaría AsuntoReal y llamaría a su petición().

Esto nos permite controlar las peticiones a AsuntoReal mediante el Proxy, por ejemplo instanciando AsuntoReal cuando sea necesario y eliminándolo cuando deje de serlo.

Patrones de comportamiento

Chain of Responsibility.

Evita acoplar el emisor de una petición a su receptor, al dar a más de un objeto la posibilidad de responder a la petición. Crea una cadena con los objetos receptores y pasa la petición a través de la cadena hasta que esta sea tratada por algún objeto.

Permite establecer la línea que deben llevar los mensajes para que los objetos realicen la tarea indicada.

Se utiliza, por ejemplo, cuando en función del estado del sistema las peticiones emitidas por un objeto deben ser atendidas por distintos objetos receptores.

Implementación

Todos los objetos receptores implementarán la misma interfaz o extenderán la misma clase abstracta. En ambos casos se proveerá de un método que permita obtener el sucesor y así el paso de la petición por la cadena será lo más flexible y transparente posible.

Command.

Encapsula una petición en un objeto, permitiendo así parametrizar a los clientes con distintas peticiones, encolar o llevar un registro de las peticiones y poder deshacer la operaciones.

Encapsula una operación en un objeto, permitiendo ejecutar dicha operación sin necesidad de conocer el contenido de la misma.

Este patrón permite solicitar una operación a un objeto sin conocer realmente el contenido de esta operación, ni el receptor real de la misma. Para ello se encapsula la petición como un objeto, con lo que además se facilita la parametrización de los métodos.

Propósito

•Encapsula un mensaje como un objeto, con lo que permite gestionar colas o registro de mensaje y deshacer operaciones.

•Soportar restaurar el estado a partir de un momento dado.

•Ofrecer una interfaz común que permita invocar las acciones de forma uniforme y extender el sistema con nuevas acciones de forma más sencilla.

Motivo

•El concepto de “orden” puede ser ambiguo y complejo en los sistemas actuales y al mismo tiempo muy extendido: intérpretes de órdenes del sistema operativo, lenguajes de macros de paquetes ofimáticos, gestores de bases de datos, protocolos de servidores de Internet, etc.

•Este patrón presenta una forma sencilla y versátil de implementar un sistema basado en comandos facilitándose su uso y ampliación.

Aplicaciones

•Facilitar la parametrización de las acciones a realizar.

•Independizar el momento de petición del de ejecución.

•Implementar CallBacks, especificando que órdenes queremos que se ejecuten en ciertas situaciones de otras órdenes. Es decir, un parámetro de una orden puede ser otra orden a ejecutar.

•Soportar el “deshacer”.

•Desarrollar sistemas utilizando órdenes de alto nivel que se construyen con operaciones sencillas (primitivas).

Interpreter.

Dado un lenguaje, define una representación de su gramática junto con un intérprete que usa dicha representación para interpretar las sentencias del lenguaje.

Dado un lenguaje, define una gramática para dicho lenguaje, así como las herramientas necesarias para interpretarlo.

Propósito Dado un lenguaje, define una representación para su gramática junto con un intérprete del lenguaje

Usos Conocidos Definir un lenguaje para representar expresiones regulares que representen cadenas a buscar dentro de otras cadenas En general, definir un lenguaje que permita representar las distintas instancias de una familia de problemas

Iterator.

Proporciona un modo de acceder secuencialmente a los elementos de un objeto agregado sin exponer su representación interna. El patrón de diseño Iterator tiene como finalidad proveer un camino para acceder a los elementos de una colección secuencial de objetos sin exponer la representación interna de dicho recorrido.

Una colección de objetos, tal como una lista deberían proveer una forma o camino de acceso a sus elementos sin exponer su representación interna.

Por otra parte, se podría necesitar también recorrer la lista de diferentes formas, dependiendo del problema al que se le deba dar solución. Pero probablemente no se quiera llenar la “interfase Lista” con operaciones que recorran sus elementos en distintas direcciones. También se podría tener más de un recorrido pendiente por recorrer en la misma lista.

El patrón Iterator permite resolver los problemas anteriormente planteados. La idea principal de este patrón es tomar la responsabilidad de acceso y recorrido de los objetos de una lista, agregando a esta última un objeto de tipo Iterator.

La Clase Iterator define una interfase que permite acceder a los elementos de una lista. Un Objeto de tipo Iterator es el encargado de ir guardando el recorrido del elemento corriente, es decir, conoce los elementos que ya se han recorrido.

Por ejemplo, una clase List necesitaría una clase ListIterator con la siguiente relación entre ellas.

Antes de instanciar la clase ListIterator se deberá proporcionar la lista de los elementos a recorrer. Una vez instanciada la clase ListIterator, se podrá tener acceso a los elementos de List, secuencialmente. La operación CurrentItem() (Item corriente), retorna el elemento corriente de la lista. Firts() inicializa el elemento corriente con el primer elemento de la lista, Next() avanza el elemento corriente a siguiente elemento de la lista y la operación IsDone() chequea que no se haya avanzado más allá del último elemento, es decir, chequea que el recorrido no haya finalizado.

Separando los mecanismos de recorrido de los objetos de la clase List permitirá definir iteradores con diferentes políticas de recorrido, sin enumerarlos dentro de la interface List.

Por ejemplo la operación FilteringListIterator permitirá el acceso sólo a aquellos elementos de la lista que cumplan alguna restricción que actuará como filtro.

Es importante notar que la lista y el iterador están asociados, y que el cliente debe conocer que esta lista podrá ser recorrida de forma diferente a otras colecciones de objetos. Además el cliente debe optar por un tipo de objetos en particular. Sería mucho mejor si se pudiera cambiar el tipo de los elementos de la lista sin necesidad de cambiar el código del cliente que la utiliza. Esto puede lograrse generalizando el concepto de iterador de manera tal que pueda soportar iteraciones polimórficas. Está ultima es una de las principales ventajas de utilizar el patrón de diseño Iterator

Como ejemplo, supongamos que tenemos una implementación especial de lista llamada SkipList. Estas listas especiales son estructuras de datos probabilísticas con características similares a los árboles balanceados. Por lo tanto deberemos definir un iterador que funcione para ambos tipos de listas.

Ejemplo de aplicación del patrón Iterator

Para llevar este objetivo a cabo, se define una clase AbstractList que provee una interface común para la manipulación de listas. De la misma manera, se necesitará una clase abstracta Iterator que defina una interface común de iteración. Luego se define una subclase concreta de la interface Iterator para las diferentes implementaciones de lista. Como resultado de esto, el mecanismo de recorrido será independiente del tipo concreto de la colección de objetos.

Aplicabilidad

El problema siguiente es cómo crear un iterador. Para ello se debe escribir el código sin tener en cuenta el tipo específico de los elementos de la lista a recorrer motivo por el cual no se podrá instanciar una lista de un tipo particular. Como solución a esto se propone que los objetos de la lista tomen la responsabilidad de crear sus propios iteradores. Esto requiere una operación adicional, llamada CreateIterator() a través de la cual el cliente pueda solicitar un objeto iterador.

Es conveniente utilizar el patrón Iterador en las siguientes situaciones:

• Para acceder al contenido de colecciones de objetos sin exponer la representación interna del recorrido realizado

• Para soportar diferentes recorridos sobre la misma colección de objetos

• Para proveer una interfaz común de recorrido de diferentes colecciones de objetos (iteración polimórfica).

Participantes

Estructura del patrón Iterator

• Iterator: define una interface de acceso y recorrido de los elementos de una lista.

• ConcreteIterator: implementa la interface Iterator y mantiene o guarda el recorrido del ítem corriente

• Aggregate: (colección) define una interface que permite crear objetos de tipo Iterator.

• ConcreteAggregate: implementa la interface de creación de objetos Iterator y retorna una instancia apropiada de la clase ConcreteIterator

Colaboraciones...

Un ConcreteIterator mantiene la pista de cuales fueron los objetos que se recorrieron y conoce el elemento siguiente al actual.

Consecuencias...

Este patrón tiene tres consecuencias importantes:

• Soporta variaciones en el recorrido de una colección, puesto que estructuras complejas pueden requerir recorridos en muchas formas. Los iteradores hacen fácil cambiar el algoritmo de recorrido, con sólo reemplazar la instancia del iterador a una diferente.

• Los iteradores simplifican la interfaz de las colecciones, ya que la interfaz de los recorridos se encuentra en los iteradores y no en la clase que corresponde a la estructura en cuestión.

• Más de un recorrido puede estar pendiente en una colección, puesto que cada iterador mantiene la pista de su recorrido. Por lo tanto, se puede tener más de un recorrido en progreso al mismo tiempo.

Implementaciones...

Los iteradores tienen muchas variantes y alternativas en cuanto a la implementación. Las ventajas dependen de las estructuras de control que el lenguaje provea. Algunas de las más importantes son las siguientes.

• ¿Quién controla la iteración?

La iteración puede ser controlada de forma interna (controlada por el iterador), o de forma externa (controlada por el cliente).

• ¿Quién define el algoritmo de recorrido?

El algoritmo puede ser definido dentro de la colección, con lo cual el iterador es utilizado sólo como cursor de la posición actual o en la clase Iterator

• Operaciones adicionales en el iterador

Se pueden adicionar operaciones a las ya básicas (First, Next, IsDone y CurrentItem)

• Iteradores para tipos compuestos (Composite)

• Iteradores nulos

Referido a un iterador útil para el manejo de condiciones de frontera.

Mediator.

Define un objeto que encapsula cómo interactúan un conjunto de objetos. Promueve un bajo acoplamiento al evitar que los objetos se refieran unos a otros explícitamente, y permite variar la interacción entre ellos de forma independiente.

Define un objeto que coordine la comunicación entre objetos de distintas clases, pero que funcionan como un conjunto.

Un Mediator (o patrón de diseño) coordina las relaciones entre sus asociados. Permite la interacción de varios objetos, sin generar acoples fuertes en esas relaciones.

Motivación

Cuando muchos objetos interactúan con otros objetos, se puede formar una estructura muy compleja, con objetos con muchas conexiones con otros objetos. En un caso extremo cada objeto puede conocer a todos los demás objetos. Para evitar esto el patrón Mediator encapsula el comportamiento de todo un conjunto de objetos en un solo objeto.

Aplicabilidad

Usar el patrón Mediator cuando:

•Un conjunto grande de objetos se comunica de una forma bien definida, pero compleja.

•Rehusar un objeto se hace difícil por que se relaciona con muchos objetos.

•El comportamiento de muchos objetos que esta distribuido entre varias clases, puede resumirse en una o varias por subclasificacion.

Memento.

Representa y externaliza el estado interno de un objeto sin violar la encapsulación, de forma que éste puede volver a dicho estado más tarde.

Permite volver a estados anteriores del sistema.

El patrón de diseño Memento, tiene como finalidad almacenar el estado de un objeto (o del sistema completo) en un momento dado de manera que se pueda restaurar en ese punto de manera sencilla. Para ello se mantiene almacenado el estado del objeto para un instante de tiempo en una clase independiente de aquella a la que pertenece el objeto (pero sin romper la encapsulación), de forma que ese recuerdo permita que el objeto sea modificado y pueda volver a su estado anterior.

Motivación

Se usa este patrón cuando se quiere poder restaurar el sistema desde estados pasados y por otra parte, es usado cuando se desea facilitar el hacer y deshacer de determinadas operaciones, para lo que habrá que guardar los estados anteriores de los objetos sobre los que se opere (o bien recordar los cambios de forma incremental).

Observer.

Define una dependencia de uno-a-muchos entre objetos, de forma que cuando un objeto cambia de estado se notifica y actualizan automáticamente todos los objetos.

Define una dependencia de uno-a-muchos entre objetos, de forma que cuando un objeto cambie de estado se notifique y actualicen automáticamente todos los objetos que dependen de él.

El patrón Observador define una dependencia del tipo uno-a-muchos entre objetos, de manera que cuando uno de los objetos cambia su estado, el observador de encarga de notificar este cambio a todos los otros dependientes.

Este patrón también se conoce como el patrón de publicación-suscripción o modelo-vista. Estos nombres sugieren las ideas básicas del patrón, que son bien sencillas: el objeto de datos, llamémoslo “Sujeto” a partir de ahora, contiene métodos mediante los cuales cualquier objeto observador o vista se puede suscribir a él pasándole una referencia a si mismo. El Sujeto mantiene así una lista de las referencias a sus observadores.

Los observadores a su vez están obligados a implementar unos métodos determinados mediante los cuales el Sujeto es capaz de notificar a sus observadores “suscritos” los cambios que sufre para que todos ellos tengan la oportunidad de refrescar el contenido representado. De manera que cuando se produce un cambio en el Sujeto, ejecutado, por ejemplo, por alguno de los observadores, el objeto de datos puede recorrer la lista de observadores avisando a cada uno.

State.

Permite que un objeto modifique su comportamiento cada vez que cambia su estado interno. Parecerá que cambia la clase del objeto.

Permite que un objeto modifique su comportamiento cada vez que cambie su estado interno.

El patrón de diseño State se utiliza cuando el comportamiento de un objeto cambia dependiendo del estado del mismo. Por ejemplo: una alarma puede tener diferentes estados, como desactivada, activada, en configuración. Definimos una interface Estado_Alarma, y luego definimos los diferentes estados.

Propósito

Permite a un objeto alterar su comportamiento según el estado interno en que se encuentre.

Motivación

El patrón State está motivado por aquellas clases que, según su estado actual varía su comportamiento ante los diferentes mensajes. Como ejemplo se toma una clase TCPConection la que representa una conexión de red, un objeto de esta clase tendrá diferentes respuestas según su estado (Listening, Close o Established). Por ejemplo una llamada al método Open de un objeto de la clase TCPConection diferirá su comportamiento si la conexión se encuentra en Close o en Established.

Solución

Se implementa una clase para cada estado diferente del objeto y el desarrollo de cada método según un estado determinado. El objeto de la clase a la que le pertenecen dichos estados resuelve los distintos comportamientos según su estado, con instancias de dichas clases de estado. Así, siempre tiene presente en un objeto el estado actual y se comunica con este para resolver sus responsabilidades.

La idea principal en el patrón State es introducir una clase abstracta TCPState que representa los estados de la conexión de red y una interfaz para todas las clases que representan los estados propiamente dichos. Por ejemplo la clase TCPEstablished y la TCPClose implementan responsabilidades particulares para los estados establecido y cerrado respectivamente del objeto TCPConection. La clase TCPConection mantiene una instancia de alguna subclase de TCPState con el atributo state representando el estado actual de la conexión. En la implementación de los métodos de TCPConection habrá llamados a estos objetos representados por el atributo state para la ejecución de las responsabilidades, así según el estado en que se encuentre, enviará estos llamados a un objeto u otro de las subclases de TCPState.

Strategy.

Define una familia de algoritmos, encapsula uno de ellos y los hace intercambiables. Permite que un algoritmo varíe independientemente de los clientes que lo usan.

Permite disponer de varios métodos para resolver un problema y elegir cuál utilizar en tiempo de ejecución.

El patrón Strategy permite mantener un conjunto de algoritmos de los que el objeto cliente puede elegir aquel que le conviene e intercambiarlo según sus necesidades.

Los distintos algoritmos se encapsulan y el cliente trabaja contra un objeto contexto o Context. Como hemos dicho, el cliente puede elegir el algoritmo que prefiera de entre los disponibles o puede ser el mismo objeto Context el que elija el más apropiado para cada situación.

Cualquier programa que ofrezca un servicio o función determinada, que pueda ser realizada de varias maneras, es candidato a utilizar el patrón Strategy. Puede haber cualquier número de estrategias y cualquiera de ellas podrá ser intercambiada por otra en cualquier momento, incluso en tiempo de ejecución.

Template Method.

Define en una operación el esqueleto de un algoritmo, delegando en las subclases algunos de sus pasos. Permite que las subclases redefinan ciertos pasos del algoritmo sin cambiar su estructura.

Define en una operación el esqueleto de un algoritmo, delegando en las subclases algunos de sus pasos, esto permite que las subclases redefinan ciertos pasos de un algoritmo sin cambiar su estructura.

Un Template Method es un patrón de diseño el cual define una estructura algorítmica en la súper clase, delegando la implementación a las subclases. Es decir, define una serie de pasos, en donde los pasos serán redefinidos en las subclases.

Propósito

Usando el Template Method, se define una estructura de herencia en la cual la superclase sirve de plantilla (”Template” significa plantilla) de los métodos en las subclases. Una de las ventajas de este método es que evita la repetición de código, por tanto la aparición de errores.

¿Cuándo Usarlo?

Este patrón se vuelve de especial utilidad cuando es necesario realizar un algoritmo que sea común para muchas clases, pero con pequeñas variaciones entre una y otras.

Visitor.

Representa una operación sobre los elementos de una estructura de objetos. Permite definir una nueva operación sin cambiar las clases de los elementos sobre los que opera.

En programación orientada a objetos, el patrón visitor es una forma de separar el algoritmo de la estructura de un objeto.

La idea básica es que se tiene un conjunto de clases elemento que conforman la estructura de un objeto. Cada una de estas clases elemento tiene un método aceptar (accept()) que recibe al objeto visitador (visitor) como argumento. El visitador es una interfaz que tiene un método visit diferente para cada clase elemento; por tanto habrá implementaciones de la interfaz visitor de la forma: visitorClase1, visitorClase2... visitorClaseN. El método accept de una clase elemento llama al método visit de su clase. Clases concretas de un visitador pueden entonces ser escritas para hacer una operación en particular.

Cada método visit de un visitador concreto puede ser pensado como un método que no es de una sola clase, sino de un par de clases: el visitador concreto y clase elemento particular. Así el patrón visitor simula el envío doble (en inglés éste término se conoce como Double-Dispatch) en un lenguaje convencional orientado a objetos de envío único (Single-Dispatch), como son Java o C++).

El patrón visitor también especifica cómo sucede la interacción en la estructura del objeto. En su versión más sencilla, donde cada algoritmo necesita iterar de la misma forma, el método accept de un elemento contenedor, además de una llamada al método visit del objeto visitor, también pasa el objeto visitor como argumento al llamar al método accept de todos sus elementos hijos.

Este patrón es ampliamente utilizado en intérpretes, compiladores y procesadores de lenguajes, en general.

El patron Visitor se utiliza cuando existe una clase padre A y una o varias clases hijas A1, A2, …, AN; todas tienen un método común, por ejemplo metodo1() que realiza diferentes tareas dependiendo del tipo de la clase. ¿Para quá puede servir esto? Pues para iterar por una lista de clases A e hijas y llamar al metodo1 en cada clase.

El problema surge en que para recorrer la lista, el tipo del objeto (item) en cada iteración debe ser de tipo A (el padre), y que si se llama a item.metodo1() ¡se ejecutara el metodo1 contenido en la clase padre! En lugar de lo correcto, que sería ejecutar el metodo1 de la clase hija a la que pertenezca el item.

La solución consiste en crear un método accept(VisitanteA visitante) en todas las clases A e hijas que acepte un “visitante” (una clase de tipo VisitanteA). En el metodo accept se llama al método visit de la clase visitante que se paso como parametro, y el unico parámetro del método visit es “this”, un puntero al objeto actual.

En la clase VisitanteA se implementa un método visit(A param), otro visit(A1 param), otro visit(A2, param), etc. Uno por cada clase que se quiera “visitar”. En ese método visit(Ax param) es dónde se ejecuta la lógica de nuestro antiguo método1(). De este modo, el Visitante “descubre” la clase del objeto, ya que el objeto se envía a si mismo y debido a la sobrecarga del método visit() se ejecutan las sentencias correctas.

Espero haberlo explicado medianamente bien. Por el momento he implementado como 4 o 5 clases visitantes. Tengo muchas herencias inusuales y restricciones semánticas en la base de datos que requieren del patrón. Todo esto me pasa por “mover” el modelo a la base de datos, en lugar de implementarlo en la lógica de negocio. Sin embargo, considero que este enfoque es mejor y más mantenible, aunque más laborioso.

...

Descargar como  txt (54.3 Kb)  
Leer 33 páginas más »
txt