Mostrando entradas con la etiqueta patrones de diseño. Mostrar todas las entradas
Mostrando entradas con la etiqueta patrones de diseño. Mostrar todas las entradas

viernes, 9 de julio de 2010

Memento command

El patrón de diseño memento es muy simple y bastante útil. Como todo en la vida, también tiene sus puntos negativos. Por esta razón existe otro patrón de diseño llamado command que soluciona esos problemas.

El patrón de diseño Memento

Supongamos que queremos hacer un sistema de Undo/Redo. Esto significa que, hagamos la acción que hagamos sobre un objeto que llamaré primario, hemos de tener la posibilidad de volver a un estado anterior. (Nota: Llamo acción a una operación que modifica el estado.)

El patrón de diseño memento resuelve este problema y es muy sencillo. Lo que hay que hacer es incluir en el objeto dos operaciones:

  • Guarda estado
  • Recupera estado

Estas operaciones no son acciones en el sentido de modificar el estado, incluso al recuperar el estado lo que hacemos es volver a un estado anterior, no modificar el actual.

La operación que guarda el estado lo que hace es generar un objeto con el estado encapsulado dentro. A este objeto se le llama el objeto memento (recuerdo). Este objeto no tiene operaciones y lo único que se puede hacer con él es pasarlo a "recupera estado". Al recuperar estado no se destruye el memento, sólo se usa para recuperar el estado.

De esta manera:

  • Se reifica el estado en un objeto memento. A partir de este momento se puede guardar, ordenar, etiquetar y, en general, gestionar los estados.
  • El objeto memento es opaco no necesitamos tener conocimiento de cómo es ese estado por dentro.
  • Objetos memento de objetos primarios de distinto tipo deberán tener distinto tipo, de esta forma se mantendría la seguridad del tipado.
  • La idea de copiar y restaurar estados es simple y fácil de entender.

Sin embargo, el patrón memento tiene un gran problema. Siempre se guarda todo el estado y este puede ser muy grande. Si quisiéramos tener un sistema de Undo/Redo que recuerde muchos estados anteriores, el problema del almacenamiento de los estados llega a ser costoso en términos de uso de memoria.

Hacia un sistema de estados diferencial

La solución obvia es hacer un sistema de cambio de estados diferencial. En este sistema sólo se guardan los cambios entre dos estados. De hecho, la operación "Undo" suele deshacer únicamente una acción cada vez. ¿Por qué guardar cambios de estado completos cuando sólo es una acción la involucrada?

Ideemos entonces un sistema que por cada acción genere un objeto "minimemento" que indique cómo se deshace esa acción desde el estado actual. Llamaré a estos objetos átomos.

Ahora las dos operaciones del objeto primario serían:

  • Obtener el átomo.
  • Realizar "undo" usando el átomo.

Gráficamente sería así:


He sido cuidadoso en distinguir los objetos que existen en memoria (son los sombreados en azul) y los que existieron pero que ahora no existen (los de relleno blanco).

Este pequeño cambio que hemos hecho conlleva una gran pérdida. Ahora no tenemos objetos memento y si queremos pasar del estado 0 al estado 3 no basta con usar el átomo C. Tenemos que usar los átomos A, B y C en ese orden. De hecho, si intentásemos usar el átomo C sobre el estado 0 tendríamos un resultado inesperado ya que los átomos sólo deshacen una acción. En concreto, el átomo C la acción que va del estado 3 al 2. Por esa razón, deshacerla tiene que ser forzosamente del estado 2 al 3. En definitiva: al usar un sistema diferencial se añade una precondición que no existía en la operación de "recuperar estado" con el patrón memento.

En principio, no pasa nada ya que de todas formas podemos volver a cualquier estado anterior y, con un pequeño cambio, posterior.

Rehaciendo estados

Supongamos que hemos aplicado con éxito los átomos A, B y C de manera que hemos vuelto al estado 3. Como ocurría con el patrón memento, esto no significa que hayamos destruido los átomos A, B y C. De hecho, podríamos usarlos para volver a aplicar las acciones y llegar de nuevo al estado 0.

Esto sólo implicaría tener que añadir una nueva operación de realizar "redo" usando un átomo.

El lector astuto habrá visto que lo que hacen los átomo es reificar las acciones. Es decir, las acciones son objetos ahora (acción=átomo). Esta idea es la que subyace al patrón de diseño command.

Existe realmente un pequeño cambio para llegar al patrón de diseño command. Nosotros venimos del patrón memento y es el objeto primario el que tiene las operaciones "undo" y "redo". En el caso del patrón command estas operaciones están en el propio átomo que guardará internamente una referencia al objeto primario.

Las ideas clave del patrón de diseño command son:

  • Las acciones son objetos. Si quieres modificar el objeto primario lo que hay que hacer es crear un objeto acción del tipo adecuado.
  • Sólo las acciones modifican el objeto primario.
  • Las acciones, al ser objetos, pueden anotarse, extenderse, heredarse, etc. Por esa razón pueden hacerse, deshacerse y rehacerse. Si fueran sólo una llamada, sólo podrían hacerse.

jueves, 22 de abril de 2010

El patrón de elemento compuesto

Hace algún tiempo hablé del patrón decorador y me quedé con las ganas de comentar otros patrones de diseño. Ahora le voy a hincar el diente al patrón de elemento compuesto que es muy simple y que en parte está relacionado con el decorador.

Cuando hablamos del patrón decorador, también introdujimos (aunque sin mencionarlo) el patrón de elemento compuesto para tener decoradores múltiples sobre un único elemento. Esto puede observarse por la relación de agregación hacia arriba en la jerarquía de clases. 

Si realmente no necesitamos decorar (es decir, evitar modificar la clase original "concrete component" en el diagrama) podemos eliminar el propio decorador "decorator" y añadir las operaciones directamente.

Como algunas de estas operaciones pueden requerir más de un operando, flexibilizamos el número de la relación de agregación. El resultado es el patrón de compuesto.


Realmente lo que tenemos aquí es similar a lo que teníamos con el decorador. Una interfaz común "component" que puede ser o bien un elemento hoja "leaf" en nuestro árbol de composición o un elemento compuesto "composite" que contiene a otros elementos en él.

A diferencia del patrón decorador, el elemento compuesto es parte del sistema y no un parche posterior. Si tuviéramos que modificar "Leaf", lo cambiaríamos.


viernes, 31 de julio de 2009

Patrón decorador

Los patrones de diseño no son más que una manera conocida de resolver problemas. Incluso sin saber lo que son, es probable que cualquier programador los haya usado sin querer. Esto es así porque muchas veces son la solución más lógica y no dependen del lenguaje de programación que se use.

El patrón decorador se basa en el siguiente problema. Tengo una clase C y por alguna razón no puedo o no quiero tocarla, pero tendría que hacer algo que no hace. Es más, esta nueva funcionalidad ni siquiera necesita que modifiquemos su interfaz I (por lo que los usuarios de la clase ni se van a enterar que estoy modificando la funcionalidad por debajo).

La solución que propone el patrón decorador es crear otra clase D que implemente esta interfaz y que envuelva a la clase C. Cuando ejecutamos los miembros del interfaz de D, hacemos lo que tengamos que hacer como si fuéramos la clase C y, si fuera necesario, usamos la clase C que envolvemos. Es decir, suplantamos la clase C.

Ya está. Fácil, ¿no? Bueno, podemos refinar porque aún tenemos algunos problemillas. Si quiero agregar varias funcionalidades a C tendría que hacer una clase D1, D2..., pero también la que combina ambas cosas D12 (Imaginemos que C es un texto y las D son el tipo de letra. Entonces D1 sería "negrita", D2 sería "cursiva" y D12 "negrita y cursiva"). Si tuviera D1, D2, D3, D4... las combinaciones serían muchas. Es lo que se denomina una explosión exponencial.

Afortunadamente, la clase D puede envolver en vez de una clase C, una clase de interfaz I. Esto incluye otras clases D. En este caso, si escribo que D envuelve a C así D(C), la modificación D12 sería D1(D2(C)). Esto me evita tener que escribir clases compuestas D12, D24, etc y evito la explosión exponencial.

Por último, y para seguir la ley DRY, es conveniente que todas las clases D1, D2... deriven de una clase D común de "envoltura básica". Esto nos lleva al esquema usual del patrón decorador tal y como viene en la Wikipedia.

Donde

  • "Component" es la interfaz común I.
  • "ConcreteComponent" es la clase C que queremos modificar pero no tocar.
  • "Decorator" es la clase base D de otros decoradores D1, D2... Esta clase lo único que hace es contener (envolver) un objeto de interfaz I.
  • "ConcreteDecorator" son las clases D1, D2... que tengamos que implementen la nueva funcionalidad.