Upgrade de wordpress

He hecho un upgrade del software de este blog (wordpress). Si alguien ve algo raro es que probablemente algo ha ido mal y se tiene que revisar.
Agradecería un simple aviso a través de un comentario aquí mismo si se da el caso.
Gracias!

Memento: estado de un objeto (I)

Introducción

Una de las funcionalidades más populares de cualquier programa que maneje datos es la de hacer/deshacer. Dicha funcionalidad se basa en la captura del estado de los distintos objetos que participan en una determinada tarea y su posterior recuperación.
La implementación de un sistema horizontal para la gestión del estado de los objetos en una aplicación es un tema complejo. El libro de Gof "Design Patterns: Elements of Reusable Object-Oriented Software" plantea una solución al problema bautizada como Memento.

Problema

Lo que queremos conseguir es capturar el estado de un objeto en un momento dado y externaizarlo de tal forma que más tarde pueda ser recuperado y devuelto al objeto. Todo esto sin romper el encapsulamiento.

Actores y colaboraciones

En el patrón de diseño Memento, según descrito en el libro de Gof, intervienen tres actores:

Implementación

Para esta implementación de ejemplo he utilizado la clase fl.controls.TextArea del set de componentes visuales de Flash y la he extendido para que tenga la capacidad de crear y recuperar Mementos.
La estructura del proyecto es la que se puede ver en la siguiente imágen:

En el package com.joangarnet.patterns.memento podemos ver una clase y dos interfaces que presumiblemente son el sistema transversal de hacer/deshacer en nuestra aplicación ficticia. Vamos a ver qué hacen.

IMemento.as

Ésta es la más las más fácil. IMemento es una interfaz vacía que nos aporta un tipo común para todas las clases Memento. Es decir, cualquier Memento en nuestra aplicación implementará esta interfaz para poder actuar cómo tal.

Actionscript:
  1. package com.joangarnet.patterns.memento {
  2.     public interface IMemento {
  3.     }
  4. }

IOriginator.as

Esta interfaz, a implementar por todos los Originators, obliga a los implementores a definir dos métodos muy importantes. Uno es createMemento y da la funcionalidad de crear y devolver un Memento a partir del estado actual del objeto, el otro es setMemento y da la funcionalidad de, a partir de un Memento dado, reestablecer el estado del objeto.

Actionscript:
  1. package com.joangarnet.patterns.memento {
  2.     public interface IOriginator {
  3.         function setMemento( memento:IMemento ):void;
  4.         function createMemento():IMemento;
  5.     }
  6. }

MementoCaretaker.as

Esta clase hace de contenedor genérico de Mementos. Dispone de tres métodos a tener en cuenta. Uno es addMemento y sirve para guardar un memento de un objeto determinado, otro es undo y sirve para reestablecer el estado de un objeto dado al último Memento guardado, finalmente tenemos redo que hace los mismo pero deshaciendo lo que hemos deshecho... La clase es un Singleton porque me ha parecido lo suficientemente justificado que lo sea ya que al fin y al cabo es desde esta clase que se gestionan todos los mementos y no tiene sentido que exista otra instancia.

Actionscript:
  1. package com.joangarnet.patterns.memento {
  2.     import flash.utils.Dictionary;
  3.  
  4.     public class MementoCaretaker {
  5.         private var undoMementos:Dictionary;
  6.         private var redoMementos:Dictionary;
  7.  
  8.         public function MementoCaretaker(se:SE) {
  9.             undoMementos = new Dictionary(true);
  10.             redoMementos = new Dictionary(true);
  11.         }
  12.  
  13.         public function addMemento( host:IOriginator, memento:IMemento ):void {
  14.             redoMementos = new Dictionary(true);
  15.            
  16.             if (! undoMementos[host]) {
  17.                 undoMementos[host] = [];
  18.             }
  19.             (undoMementos[host] as Array).push(memento);
  20.         }
  21.  
  22.         public function undo( host:IOriginator ):IMemento {
  23.             if (canUndo(host)) {
  24.                 if (! redoMementos[host]) {
  25.                     redoMementos[host] = [];
  26.                 }
  27.                 var lastMemento:IMemento = (undoMementos[host] as Array).pop();
  28.                 (redoMementos[host] as Array).push( lastMemento );
  29.                 return lastMemento;
  30.             }
  31.             return null;
  32.         }
  33.        
  34.         public function redo( host:IOriginator ):IMemento {
  35.             if (canRedo(host)) {
  36.                 var nextMemento:IMemento = (redoMementos[host] as Array).pop();
  37.                 (undoMementos[host] as Array).push( nextMemento );
  38.                 return nextMemento;
  39.             }
  40.             return null;
  41.         }
  42.  
  43.         public function canUndo( host:IOriginator ):Boolean {
  44.             return undoMementos[host] && (undoMementos[host] as Array).length> 0;
  45.         }
  46.        
  47.         public function canRedo( host:IOriginator ):Boolean {
  48.             return redoMementos[host] && (redoMementos[host] as Array).length> 0;
  49.         }
  50.        
  51.         public function getRedos( host:IOriginator ):uint {
  52.             if( !redoMementos[host] ) {
  53.                 return 0;
  54.             }
  55.             return (redoMementos[host] as Array).length;
  56.         }
  57.        
  58.         public function getUndos( host:IOriginator ):uint {
  59.             if( !undoMementos[host] ) {
  60.                 return 0;
  61.             }
  62.             return (undoMementos[host] as Array).length;
  63.         }
  64.  
  65.         // Singleton implementation
  66.         private static var instance:MementoCaretaker;
  67.         public static function getInstance():MementoCaretaker {
  68.             return instance || (instance = new MementoCaretaker(new SE()));
  69.         }
  70.     }
  71. }
  72. class SE{/* Singleton Enforcer */}

En el package com.joangarnet.flash.controls tenemos la parte más específica en la que sacamos partido a la implementación genérica de memento que ya tenemos hecha.
Para este ejemplo he escogido el TextArea porque se parece a un editor de textos, que es a lo que más acostumbrados estamos todos a ver con funcionalidad de hacer/deshacer. ¿Qué sería un editor de textos sin hacer/deshacer? de hecho nuestro textarea será mejor que el viejo Notepad de Windows, que solo tiene un único deshacer! Es eso posible todavía? pues se ve que si...

UndoableTextArea.as

Éste es nuestro primer Originator. Otros candidatos serían UndoableTextInput, UndoableColorPicker, UndoableSlider, etc... Cómo buen originator debe implementar y satisfacer la interfaz IOriginator.

Actionscript:
  1. package com.joangarnet.flash.controls {
  2.     import com.joangarnet.patterns.memento.IMemento;
  3.     import com.joangarnet.patterns.memento.IOriginator;
  4.    
  5.     import fl.controls.TextArea;
  6.    
  7.     public class UndoableTextArea extends TextArea implements IOriginator {
  8.         public function UndoableTextArea() {
  9.             super();
  10.         }
  11.        
  12.         public function setMemento( memento:IMemento ):void {
  13.             if (memento) {
  14.                 var m:UndoableTextAreaMemento = memento as UndoableTextAreaMemento;
  15.                 this.text = m.text;
  16.                 this.verticalScrollPosition =  m.verticalScrollPosition;
  17.                 this.horizontalScrollPosition = m.horizontalScrollPosition;
  18.             }
  19.         }
  20.        
  21.         public function createMemento():IMemento {
  22.             return new UndoableTextAreaMemento( this.text, this.verticalScrollPosition, this.horizontalScrollPosition );
  23.         }
  24.     }
  25. }

UndoableTextAreaMemento.as

Si tenemos un Originator para un TextArea entonces es obligado disponer de su Memento. Éste es el papel de esta clase. Cosas a notar de la implementación de una clase Memento. Primero debe implementar la interfaz IMemento, segundo todas las propiedades de estado que se le asignan se pasan como parámetros al constructor y son luego accesibles a través de getters. Todo esto es para mantener el encapsulamiento. Como norma general es preferible que los mementos guarden propiedades de tipos primitivos. Esto ayudará por ejemplo a evitar que por error se pueda llegar a modificar el estado del memento a través de una referencia, o también nos permitirá más tarde añadir una capa de persistencia fácilmente (lo veremos en otro post).

Actionscript:
  1. package com.joangarnet.flash.controls
  2. {
  3.     import com.joangarnet.patterns.memento.IMemento;
  4.    
  5.     public class UndoableTextAreaMemento implements IMemento {
  6.         private var _horizontalScrollPosition:Number;
  7.         private var _verticalScrollPosition:Number;
  8.         private var _text:String;
  9.        
  10.         public function UndoableTextAreaMemento( text:String, verticalScrollPosition:Number, horizontalScrollPosition:Number ) {
  11.             this._verticalScrollPosition = verticalScrollPosition;
  12.             this._horizontalScrollPosition = horizontalScrollPosition;
  13.             this._text = text;
  14.         }
  15.        
  16.         public function get horizontalScrollPosition():Number {
  17.             return _horizontalScrollPosition;
  18.         }
  19.        
  20.         public function get verticalScrollPosition():Number {
  21.             return _verticalScrollPosition;
  22.         }
  23.        
  24.         public function get text():String {
  25.             return _text;
  26.         }
  27.     }
  28. }

MementoSample_1.as

Finalmente el punto de entrada a la aplicación. La clase que Flash tiene asignada como DocumentClass ( creo que se llama así... ) y que monta el ejemplo.
Aquí la gestión de los mementos es externa a su Originator pero no habría ningún problema en que cada Originator escuchara a los eventos de teclado y se autogestionara los mementos...

Actionscript:
  1. package {
  2.     import com.joangarnet.flash.controls.UndoableTextArea;
  3.     import com.joangarnet.patterns.memento.MementoCaretaker;
  4.    
  5.     import fl.controls.Button;
  6.    
  7.     import flash.display.Sprite;
  8.     import flash.events.Event;
  9.     import flash.events.MouseEvent;
  10.  
  11.     public class MementoSample_1 extends Sprite {
  12.         private var tf:UndoableTextArea;
  13.         private var undo:Button;
  14.         private var redo:Button;
  15.  
  16.         public function MementoSample_1() {
  17.             undo = new Button();
  18.             undo.label = "undo (0)";
  19.             undo.x = 30; undo.y = 25;
  20.             undo.addEventListener( MouseEvent.CLICK, undoHandler );
  21.            
  22.             redo = new Button();
  23.             redo.label = "redo (0)";
  24.             redo.x = undo.x + undo.width + 5; redo.y = 25;
  25.             redo.addEventListener( MouseEvent.CLICK, redoHandler );
  26.  
  27.             tf = new UndoableTextArea();
  28.             tf.x = 30; tf.y = 50;
  29.             tf.width = 200; tf.height = 100;
  30.             tf.addEventListener( Event.CHANGE, changeHandler );
  31.  
  32.             addChild( undo );
  33.             addChild( redo );
  34.             addChild( tf );
  35.         }
  36.  
  37.         private function changeHandler( event:Event ):void {
  38.             MementoCaretaker.getInstance().addMemento( tf, tf.createMemento() );
  39.             printAvailableMementos();
  40.         }
  41.  
  42.         private function undoHandler( event:MouseEvent ):void {
  43.             tf.setMemento( MementoCaretaker.getInstance().undo(tf) );
  44.             printAvailableMementos();
  45.         }
  46.        
  47.         private function redoHandler( event:MouseEvent ):void {
  48.             tf.setMemento( MementoCaretaker.getInstance().redo(tf) );
  49.             printAvailableMementos();
  50.         }
  51.  
  52.         private function printAvailableMementos():void {
  53.             undo.label = "undo (" + MementoCaretaker.getInstance().getUndos(tf).toString() + ")";
  54.             redo.label = "redo (" + MementoCaretaker.getInstance().getRedos(tf).toString() + ")";
  55.         }
  56.     }
  57.  
  58. }

Conclusión

Como se puede ver una vez se tiene la base es muy sencillo implementar esta funcionalidad. Hacerlo para otros componentes del set de componentes de flash es una cuestión tan trivial como saber determinar qué propiedades queremos guardar en un memento, crear el Memento y la subclase del componente escogido para que implemente IOriginator.
En una entrada próxima creo que será interesante evolucionar un poco más la implementación de este patrón de diseño para añadir por ejemplo una capa de persistencia y quizás abstraer incluso más la gestión de los Mementos para evitar tener que crear un Memento para cada clase Originator.

UPDATE: ver parte 2: "Memento: estado de un objeto (II)"

Descarga ejemplo

En el zip hay proyectos para Flash CS4 y para Flex 3.

[OT] Yo no soy Joan Garnet

No es broma, aunque algunos conocen ya mi doble personalidad todavía tengo pequeños malentendidos con este tema (como es normal) y creo que no está de más contar qué pasa con mi nombre...
Por si alguien no se ha pasado todavía por la página de about y ha leído las cuatro líneas que escribí en su día mi nombre real es Joan Llenas Masó.
Joan Garnet es un nickname que se compone de mi nombre de pila 'Joan' y la palabra 'garnet' que en inglés significa, entre otras cosas, granate.

LiteDays: Eventos en vivo de BlocketPc

BlocketPC Lite daysEl SMAUG BlocketPC ha organizado un evento especial para celebrar su tercer aniversario como AUG. El evento va girar entorno a la Plataforma Flash y su integración con dispositivos y móbiles, un tema del que el equipo de BlocketPC siempre nos ha tenido al día y que sin duda ahora, más que nunca, va a dar mucho de que hablar.

Si quieres saber las últimas novedades acerca de este tema de la mano del mismisimo Mark Doherty (Platform Evangelist at Adobe on Mobile and Devices), de Zed, de Thumbplay o del propio equipo de BlocketPC solo tienes que registrate. Es totalmente gratuito e indoloro, lo peor que te puede pasar es que te vuelvas a casa con un montón de información útil acerca de cómo vas a integrar tu nueva killer app en la nevera de casa o con uno de los premios que se sortearán al finalizar el evento entre los asistentes: un Adobe Web Premium CS4, un Nokia E71 o merchandising.