« Un ejemplo del patrón memento ( la versión java ) | Inicio | El fotograma en blanco y mis noches de insomnio. »

Un ejemplo del patrón memento ( la versión actionscript )

Conquistar el mundo no es fácil. Nada fácil. Yo lo sé, tú lo sabes, incluso el Profesor Dispar lo sabe.

El Profesor se siente preparado para llevar a cabo su malvado plan. Tiene el conocimiento teóricos, tiene los conocimientos prácticos, tiene un plan, tiene hasta unas gafas de sol nuevas, pero ¡hay tantos detalles que pulir antes de lanzarse a la conquista del mundo!.

En episodios anteriores, hemos visto cómo el Profesor ha implementado el patrón prototype ( para crear su ejército de clones -¡anda, acabo de caer!- ), el patrón extensión objects ( para asignarles sus roles ), el patrón command ( para asignarles las órdenes ), y el patrón observer ( para implementar el sistema de comunicaciones ). Parece que el Profesor Dispar ha estado bastante ocupado implementando patrones, pero ha sido suficiente?. NO!! ( muhahahahahahhaha ).

Si recuerdas el post sobre el patrón observer ( cosa que dudamos mucho, porque nosotros ya no nos acordamos ), dejamos a las vacas y las ovejas en el instante previo al ataque final ( mientras el Profesor reía como un histérico ). Están esperando ( insértese aquí música heroica, por favor, en dramático crescendo ) a recibir la orden de ataque. Cada vaca, cada oveja, está alerta, escuchando la radio, esperando escuchar la señal secreta, para abandonar su posición y lanzarse a cumplir las órdenes recibidas.

El Profesor Dispar está a punto de presionar el botón de "atacar", cuando de repente se da cuenta de que algo no está bien ( y la música heroica se apaga ). “¿Qué pasa si me veo obligado a dar la orden de retirada a mis tropas?. No es que mi plan vaya a fallar ( después de todo soy un genio del mal ), pero ya se sabe, no se puede confiar en los subordinados, y qué pasa si tengo que cancelar el ataque cuando mis huestes ya han comenzado a avanzar ( glorioso avance, por supuesto )?”.

Ciertamente, el Profesor Dispar es un genio. Se ha dado cuenta de un sutil “bug” en su plan. ¿Qué pasa si tiene que dar la orden de cancelar el ataque cuando sus tropas ya han abandonado su posición inicial?. Bueno, ha implementado un mecanismo de comunicaciones, así que puede mandar la señal secreta de retirada, ¿no?. ( muhahahahahah, ya sabes ). Pero hay un problema: la vacas y las ovejas son conocidas por su notoria falta de memoria ( peor incluso que los peces ). Claro, que también son conocidas por la calidad de los quesos que se producen con su leche, pero eso está fuera del alcance de este tutorial.

La cuestión es complicada. Una vaca no tiene memoria. Una oveja no tiene memoria. Punto-pelota. Sólo pueden recordar una cosa a la vez. Por tanto, pueden recordar que tienen que atacar, que tienen que moverse hacia algún lugar, pero en cuanto se meten esa información en la mollera no son capaces de recordar nada más ( como, por ejemplo, dónde estaban cinco minutos antes, o si le habían pedido dinero prestado a alguien ).

Dicho de otra forma, el Profesor Dispar les puede decir que vuelvan a su posición original, pero eso no va a servir de nada, porque no se acuerdan de cuál era su posición original.

memento1.jpg

Peeeeeero ( y vuelve a empezar la música heroica ), el Profesor Dispar recuerda vagamente, como entre una nebulosa, sus tiempos mozos de estudiante, cuando en su clase explicaron el patrón memento.

¿Qué pasaría si cada oveja y cada vaca fuera capaz de escribir en un cuaderno ( cada una en su cuaderno, será por dinero… ) su posición inicial, y entregara ese cuaderno a su Sargento?. ¿Y si el Sargento guardara esos cuadernos, y se los entregara a sus propietarios si los propietarios tuvieran que volver a las posiciones iniciales?. ¡Problema resuelto! ( muhahahahahahah ). ¡Es perfecto!. Las ovejas y las vacas sólo tendrán que recordar una posición ( la posición hacia la que se supone que tiene que ir, sin importar si están atacando o están en retirada ), mientras una entidad externa les guardará la información que necesitarán para retirarse.

memento2.jpg

¿Pero cómo puede la oveja guardar su información relevante en un cuaderno?. Fácil ( aparte de las limitaciones fisiológicas propias de su condición ). Cada oveja será responsable de crear una instancia de la clase en la que se va a guardar su información interna, y la guardará y después se la pasará a su sargento.

import sheep.Sheep class sheep.SheepMemento { private var serialNumber: Number; private var location: String; private var creator: Sheep; function SheepMemento( serialNumber: Number, location: String ) { this.serialNumber = id; this.location = location; } public function setLocation( newLocation: String ) { this.location = newLocation; } public function getState( creatorRef: Sheep ) { return { serialNumber: this.serialNumber, location: this.location }; }

La clase Sheep

import sheep.SheepMemento class sheep.Sheep { var serialNumber: Number; var location: String; function Sheep( serial: Number, loc: String ) { this.serialNumber = serial; this.location = loc; } public function getMemento( ): SheepMemento { return new SheepMemento( this.serialNumber, this.location ); } public function setMemento( state: SheepMemento ) { var stateData: Object = state.getState( ); this.serialNumber = stateData.serialNumber; this.location = stateData.location; } //Public setters. Change inner state public function setLocation( newLocation: String ) { this.location = newLocation; } //For debugging purposes public function toString( ): String { return "Sheep id: " + this.serialNumber + " @ " + this.location; } }

Y finalmente, el sargento:

import sheep.Sheep import sheep.SheepMemento class sergeant.Sergeant { function Sergeant( ) { trace( "grrrrr, I'm the Sergeant" ); } public function attack( ) { var sheep: Sheep = new Sheep( 1, "here!" ); trace( sheep ); var sheepMemento: SheepMemento = sheep.getMemento( ); sheep.setLocation( "theeeeeere" ); trace( sheep ); sheep.setMemento( sheepMemento ); trace( sheep ); } public static function start( ) { var sergeant: Sergeant = new Sergeant( ); sergeant.attack( ); } }

¡Un momento!. El Profesor se ha dado cuenta de que hay un punto débil en su plan. Si el Sargento guarda los cuadernos con las posiciones iniciales de las ovejas, hay riesgos de seguridad. ¿Qué pasa si los pesados del enemigo roban esa información?. ¡¡O incluso peor!!. ¿Y si los pesados del enemigo no roban esa información, pero la sustituyen por información falsa?. ¡Un momento!. Si la información contenida en los cuadernos estuviera encriptada, o sólo fuera accesible por las ovejas, ¡el problema estaría resuelto, nadie podría cambiarla!.

Por tanto, para evitar posibles cambios en esa información, el Profesor va a tomar dos medidas drásticas. Por un lado, todos los valores guardados en la clase SheepMemento sólo se podrán asignar a través del constructor. De ese modo, el Profesor se asegura que sólo se podrán asignar al crear la clase, y nunca después.

Pero desgraciadamente, no es suficiente ( es tan difícil conseguir un buen plan ). El Profesor Dispar quiere evitar que la información guardada en el memento sea modificada. De hecho, quiere que sólo sea una oveja la que pueda crear su memento correspondiente, y asignar sus valores. De esa forma, puede asegurarse de que nadie modifique esa información.

Hacer eso en Java es bastante fácil. Basta con colocar la clase SheepMemento en el mismo package que la clase Sheep, y hacer tanto su constructor como sus variables de clase protected. De esa forma, sólo pueden crear instancias de esa clase otras clases que estén en su mismo paquete.

Pero hacer lo mismo en actionscript no es tan fácil. No existe el modificador “protected” ( ni su funcionalidad ). ( se para la música heroica )

El Profesor Dispar ( la música heroica comienza de nuevo ) es un programador flash y lo ha sido durante muchos años, así que su capacidad para buscar soluciones a los problemas irresolubles es legendaria. Por tanto, enseguida encuentra una posible solución. ¿Pero cómo?. El Profesor piensa: “si la oveja pasa una referencia a sí misma cuando crea el memento, puede chequear esa referencia otra vez cuando tiene que volver a su estado anterior, y de esa forma asegurar que ha sido ella la que creó el memento que se le pasa” ( muhahahahahahhahahaha ). Se lo intenta explicar a la oveja, pero ya se sabe, hay veces que si quieres hacer algo bien, lo tienes que hacer tú mismo…

Por tanto, cambia la forma en la que la oveja crea el memento, y la forma en la que vuelve a su estado anterior a partir de la información guardada en el mismo ( que es la parte más importante ). Antes de devolver los datos correspondientes, se chequea si quien pide esos datos es el mismo que el que creó el memento. Si lo es, se devuelven datos, y si no lo es, dice “beeeee” ( la cosa no da para más ).

Por tanto, finalmente la cosa quedará así ( lo primero de todo, la clase SheepMemento ):

import sheep.Sheep class sheep.SheepMemento { private var serialNumber: Number; private var location: String; private var creator: Sheep; function SheepMemento( serialNumber: Number, location: String, creator: Sheep ) { this.serialNumber = id; this.location = location; this.creator = creator; } public function getState( creatorRef: Sheep ) { var returnValue: Object = null; if( this.creator == creatorRef ) { returnValue = { serialNumber: this.serialNumber, location: this.location }; } return returnValue; } }

La clase Sheep:

import sheep.SheepMemento class sheep.Sheep { var serialNumber: Number; var location: String; function Sheep( serial: Number, loc: String ) { this.serialNumber = serial; this.location = loc; } public function getMemento( ): SheepMemento { return new SheepMemento( this.serialNumber, this.location, this ); } public function setMemento( state: SheepMemento ) { var stateData: Object = state.getState( this ); if( stateData != null ) { this.serialNumber = stateData.serialNumber; this.location = stateData.location; } else { trace( "memento not valid" ); } } //Public setters. Change inner state public function setLocation( newLocation: String ) { this.location = newLocation; } //For debugging purposes public function toString( ): String { return "Sheep id: " + this.serialNumber + " @ " + this.location; } }

Y el sargento:

import sheep.Sheep import sheep.SheepMemento class sergeant.Sergeant { function Sergeant( ) { trace( "grrrrr, I'm the Sergeant" ); } public function attack( ) { var sheep: Sheep = new Sheep( 1, "here!" ); trace( sheep ); var sheepMemento: SheepMemento = sheep.getMemento( ); sheep.setLocation( "theeeeeere" ); trace( sheep ); sheep.setMemento( sheepMemento ); trace( sheep ); } public static function start( ) { var sergeant: Sergeant = new Sergeant( ); sergeant.attack( ); } }

Muhahahahahahhaha. ¡¡El Profesor Dispar lo ha conseguido de nuevo!!. Ha sido capaz de encapsular el estado interno de un objeto en otro objeto distinto, y encapsularlo tan bien que ese segundo objeto sólo puede ser utilizado por el objeto que lo creó ( quien sea capaz de repetir la frase tendrá mi eterna admiración ).

Sólo una cosa más. ¿Qué te parece la forma en la que el Profesor ha implementado el chequeo de la identidad de la oveja?. ¿Te gusta la solución?.

En principio podría resolverse también de la siguiente forma: en vez de guardar en el memento una referencia al creador del mismo, se puede guardar el id del creador, o un hashcode de ese objeto, o algo similar. ¿Alguna idea?

Comentarios

Hay un bug en la clase SheepMemento.
En el constructor donde dice "this.serialNumber = id;" debería decir "this.serialNumber = serialNumber;"

Premio para el caballero!

Gracias, Fernando.