Un patrón de diseño resulta ser una solución a un problema de diseño.
Para que una solución sea considerada un patrón debe poseer ciertas
características. Una de ellas es que debe haber comprobado su efectividad resolviendo problemas
similares en ocasiones anteriores. Otra es que debe ser reutilizable, lo que significa que es aplicable a diferentes
problemas de diseño en distintas circunstancias.
Patrones Estructurales y de Comportamiento
Ingenieria del Software II
domingo, 27 de octubre de 2013
Patrones de Diseño: Estructurales y de Comportamiento
Los patrones de diseño son
la base para la búsqueda de soluciones a problemas comunes en el desarrollo de software y otros ámbitos referentes al diseño de interacción o
interfaces.
Patrones Estructurales: Flyweight y Proxy
- Flyweight (Peso ligero): Reduce la redundancia cuando gran cantidad de objetos poseen idéntica información.
El patrón Flyweight (u objeto ligero) sirve para
eliminar o reducir la redundancia cuando tenemos gran cantidad de objetos que
contienen información idéntica, además de lograr un equilibrio entre
flexibilidad y rendimiento (uso de recursos).
Representación gráfica del Flyweight.
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 Pelota Flyweight, que contendrá la información común (radio y
color) y otra clase Pelota Concreta, que contendrá las coordenadas concretas de
cada pelota y una referencia a un objeto de tipo Pelota Flyweight.
Al crearse instancias de PelotaConcreta, se les deberá proveer de referencias a
la instancia de Pelota Flyweight adecuada a nuestras necesidades.
En este caso solamente tendríamos una instancia de Pelota Flyweight, 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 Pelota Concreta
con determinadas características (mismo radio y color que el solicitado),
compruebe si ya existe un Pelota Flyweight 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 Pelota Flyweight existentes.
Pasos para aplicar el patrón
1. Asegúrese que el
rendimiento en los objetos es un tema primordial, y si el cliente está
dispuesto a asumir el reajuste
2. Divida el objetivo
principal en estados: Estado Intrínseco (elementos que se puedan compartir o
son comunes) y Estado Extrínseco (elementos particulares a cada tipo)
3. Retire los elementos
con estado extrínseco de los atributos de la clase, y añádale más bien una
llamada a métodos
4. Crear una fábrica
que pueda almacenar y reutilizar las instancias existentes de clases
5. El cliente debe usar
la fábrica en vez de utilizar el operador new si requiere de creación de
objetos
6. El cliente (o un
tercero) debe revisar los estados extrínsecos, y reemplazar esos estados a
métodos de la clase
Ventajas y Desventajas
Ventajas: Reduce en
gran cantidad el peso de los datos en un servidor
Desventajas: Consume un
poco más de tiempo para realizar las búsqueda
- Proxy:
Mantiene un representante de un objeto.
El patrón Proxy es un patrón estructural que tiene
como propósito proporcionar un subrogado o intermediario de un objeto para
controlar su acceso.
Representación gráfica de Proxy
Motivación
Para explicar la motivación del uso de este patrón veamos un escenario donde su
aplicación sería la solución más adecuada al problema planteado. Consideremos
un editor que puede incluir objetos gráficos dentro de un documento. Se
requiere que la apertura de un documento sea rápida, mientras que la creación
de algunos objetos (imágenes de gran tamaño) es cara. En este caso no es
necesario crear todos los objetos con imágenes nada más abrir el documento
porque no todos los objetos son visibles. Interesa por tanto retrasar el coste
de crear e inicializar un objeto hasta que es realmente necesario (por ejemplo,
no abrir las imágenes de un documento hasta que no son visibles). La solución
que se plantea para ello es la de cargar las imágenes bajo demanda. Pero, ¿cómo
cargar las imágenes bajo demanda sin complicar el resto del editor? La
respuesta es utilizar un objeto proxy. Dicho objeto se comporta como una imagen
normal y es el responsable de cargar la imagen bajo demanda.
Aplicabilidad
El patrón proxy se usa cuando se necesita una referencia a un objeto más
flexible o sofisticado que un puntero. Dependiendo de la función que se desea
realizar con dicha referencia podemos distinguir diferentes tipos de proxies:
- proxy remoto: representante local de un objeto remoto.
- proxy virtual: crea objetos costosos bajo demanda (como la clase Imagen Proxy en el ejemplo de motivación).
- proxy de protección: controla el acceso al objeto original proxy de referencia inteligente: sustituto de un puntero que lleva a cabo operaciones adicionales cuando se accede a un objeto (ej. contar número de referencias al objeto real, cargar un objeto persistente bajo demanda en memoria, control de concurrencia de acceso tal como bloquear el objeto para impedir acceso concurrente, …).
Colaboraciones
Dependiendo de la clase de proxy, el objeto proxy redirige las peticiones
al objeto real que representa.
Ejemplos de
funcionamiento:
- Diagrama de clases para un ejemplo del patrón proxy.
- Diagrama de secuencia para un ejemplo en el que no se utiliza el patrón proxy.
- Diagrama de secuencia para un ejemplo en el que se utiliza el patrón proxy.
Consecuencias
El uso de un proxy introduce un nivel de indirección adicional con diferentes
usos:
- Un proxy remoto oculta el hecho de que un objeto reside en otro espacio de direcciones.
- Un proxy virtual puede realizar optimizaciones, como la creación de objetos bajo demanda.
- El proxy de protección y las referencias inteligentes permiten realizar diversas tareas de mantenimiento adicionales al acceder a un objeto.
Además, su uso también permite realizar una optimización COW (copy-on-write) ,
puesto que copiar un objeto grande puede ser costoso, y si la copia no se
modifica, no es necesario incurrir en dicho gasto. Además el sujeto mantiene un
número de referencias, y sólo cuando se realiza una operación que modifica el
objeto, éste se copia. Es útil por tanto para retrasar la replicación de un
objeto hasta que cambia.
Patrones relacionados
- El patrón Adaptador proporciona una interfaz diferente al objeto que adapta, mientras que el proxy tiene la misma interfaz, pero ambos redirigen la petición del cliente al verdadero sujeto que la ejecuta con la posibilidad de incorporar lógica adicional : comprobación de acceso, creación del sujeto real…
- El Proxy se puede diseñar de manera similar al patrón decorador, pero el propósito es diferente: el decorador añade responsabilidades a un objeto, el proxy sólo controla su acceso. Así, si el proxy no tiene una fuerte dependencia con el sujeto real (por ejemplo, no es de creación), y no tiene que instanciarlo, puede adoptar el mismo diseño que el decorador, y ser un proxy de cualquier sujeto (referencia a la interfaz que el cliente conoce).
Ejemplos comunes de la aplicación del patrón proxy
A continuación se presentan algunos de los ejemplos más comunes en los que se
utiliza el patrón proxy:
- Añadir acceso de seguridad a un objeto existente. El proxy determinará si el cliente puede acceder al objeto de interés (proxy de protección).
- Proporcionando interfaz de recursos remotos como el servicio web o recursos REST.
- Coordinación de las operaciones costosas en recursos remotos pidiendo los recursos a distancia para iniciar la operación tan pronto como sea posible antes de acceder a los recursos.
- Agregar una operación segura para los subprocesos a una clase existente sin cambiar el código de la clase existente.
Patrones de comportamiento: Chain of Responsibility, Command e Interpreter
Chain
of Responsibility (Cadena de
responsabilidad): Permite establecer la línea que deben llevar los mensajes
para que los objetos realicen la tarea indicada.
El patrón de diseño
Chain of Responsibility es un patrón de comportamiento que evita acoplar el
emisor de una petición a su receptor dando a más de un objeto la posibilidad de
responder a una petición. Para ello, se encadenan los receptores y pasa la
petición a través de la cadena hasta que es procesada por algún objeto. Este
patrón es utilizado a menudo en el contexto de las interfaces gráficas de
usuario donde un objeto puede contener varios objetos. Según si el ambiente de
ventanas genera eventos, los objetos los manejan o los pasan.
Aplicabilidad
El patrón Cadena de
Responsabilidad debe usarse cuando:
- hay más de un objeto que puede manejar una petición, y el manejador no se conoce a priori, sino que debería determinarse automáticamente.
- se quiere enviar una petición a un objeto entre varios sin especificar explícitamente el receptor.
- el conjunto de objetos que pueden tratar una petición debería ser especificado dinámicamente.
Estructura
Participantes
- Manejador: define una interfaz para tratar las peticiones. Opcionalmente, implementa el enlace al sucesor.
- ManejadorConcreto: trata las peticiones de las que es responsable; si el ManejadorConcreto puede manejar la petición, lo hace; en caso contrario la reenvía a su sucesor.
- Cliente: inicializa la petición a un Manejador Concreto de la cadena.
Las ventajas de este patrón son:
- Reduce el acoplamiento. El patrón libera a un objeto de tener que saber qué otro objeto maneja una petición. Ni el receptor ni el emisor se conocen explícitamente entre ellos, y un objeto de la cadena tampoco tiene que conocer la estructura de ésta. Por lo tanto, simplifica las interconexiones entre objetos. En vez de que los objetos mantengan referencias a todos los posibles receptores, sólo tienen una única referencia a su sucesor.
- Añade flexibilidad para asignar responsabilidades a objetos. Se pueden añadir o cambiar responsabilidades entre objetos para tratar una petición modificando la cadena de ejecución en tiempo de ejecución. Esto se puede combinar con la herencia para especializar los manejadores estáticamente.
Por otra parte presenta el inconveniente de no garantizar la recepción. Dado
que las peticiones no tienen un receptor explícito, no hay garantías de que
sean manejadas. La petición puede alcanzar el final de la cadena sin haber sido
procesada.
Implementación
- Implementación de la cadena sucesora. Hay dos formas posibles de implementarla:
- Definir nuevos enlaces (normalmente en el Manejador, pero también podría ser en los objetos ManejadorConcreto).
- Usar enlaces existentes (otras asociaciones existentes). Por ejemplo, en el patrón Composición puede existir ya un enlace al padre que puede utilizarse para definir la cadena de responsabilidad sin necesidad de añadir otra asociación.
- Conexión de los sucesores. Si no hay referencias preexistentes para definir una cadena, entonces tendremos que introducirlas nosotros mismos. En este caso, el Manejador define la interfaz y además, se encarga de mantener el sucesor. Esto permite que el manejador proporcione una implementación predeterminada de ManejarPetición que reenvíe la petición al sucesor (si hay alguno). Si una subclase de ManejadorConcreto no está intersada en dicha petición, no tiene que redefinir la operación de reenvío.
- Representación de peticiones. Hay varias opciones para representar las peticiones:
- Una petición es una invocación a una operación insertada en el código. Esto resulta conveniente y seguro, pero sólo se pueden reenviar el conjunto prefijado de peticiones que define la clase Manejador.
- Una única función manejadora que reciba un código de petición como parámetro. Esto permite un número arbitrario de peticiones pero emisor y receptor deben ponerse de acuerdo sobre cómo codificarse la petición
Command (Orden): Encapsula una operación en un objeto,
permitiendo ejecutar dicha operación sin necesidad de conocer el contenido de
la misma.
Intención
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).
Estructura
Interpreter (Intérprete): Dado un lenguaje, define una gramática para dicho lenguaje, así como las herramientas necesarias para interpretarlo.
El interpreter es un patrón de diseño que, dado un lenguaje, define una representación para su gramática junto con un intérprete del lenguaje.
Se usa para definir un lenguaje para representar expresiones regulares que representen cadenas a buscar dentro de otras cadenas. Además, en general, para definir un lenguaje que permita representar las distintas instancias de una familia de problemas.
Motivación
Existen problemas particulares que se pueden expresar en función de algún Lenguaje.A veces es conveniente representar un lenguaje como palabras de algún lenguaje sencillo, como por ejemplo, evaluar expresiones booleanas.
El patrón Interpreter describe como definir una gramática, como representar palabras del lenguaje y como interpretarlas.
Participantes
• Cliente: Construye el árbol sintáctico abstracto de expresiones no terminales, e instancias de la clase TerminalExpresion. Luego inicializa el contexto e invoca al Interpretador.
• AbstractExpresion: Es la clase abstracta a través de la cual el cliente interactúa con las expresiones.
• TerminalExpresion: La implementación de la clase abstracta AbstractExpresion para nodos terminales en la gramática y el árbol de sintaxis.
• NonTerminalExpression: Es otra implementación de la clase abstracta para nodos no terminales de la gramática y el árbol de sintaxis. Mantiene una referencia a la siguiente expresión e invoca el método interpret en cada uno de sus hijos.
• Context: El contenedor de la información que se necesita en distintos lugares del interprete. Puede servir como un canal de comunicación entre distintas instancias de Expression.
Aplicabilidad
• Se utiliza cuando hay un lenguaje que representar cuyas sentencias se pueden representar como árboles
• La gramática es simple, de lo contrario la jerarquía de clases resulta demasiado grande y compleja
• La eficiencia no es un problema crítico
• Se obtienen soluciones más eficientes utilizando otras representaciones en lugar de árboles de análisis, como, por ejemplo, máquinas de estados (aunque éstas se pueden generar a partir de árboles construidos con este patrón)
Consecuencias
• Es fácil cambiar y ampliar la gramática.
• Resulta fácil implementarla.
• Las gramáticas complejas son difíciles de mantener.
• Añadir nuevos modos de interpretar expresiones.
Estructura
Suscribirse a:
Entradas (Atom)




