« Cómo utilizar los patrones de diseño | Inicio | Un ejemplo del patrón observer (la versión Java) »

Un ejemplo el patrón observer (la versión actionScript)

Está desencadenado. El primer ataque ha sido lanzado. El profesor Dispar ha dado las órdenes a sus huestes para dominar el mundo. En post anteriores hemos visto como el profesor Dispar ha conseguido clonar cualquier animal ( con gran predilección por ovejas y vacas ) utilizando un patrón prototype, ha conseguido darlas un rol dinámicamente con el patrón extension objects, y ha repartido las órdenes con un patrón command.

Pero como ya sabemos, el profesor Dispar está loco, pero no es idiota. Sabe, que algo puede salir mal, que un pequeño detalle puede truncar sus planes de dominar el mundo. Y también sabe que una retirada a tiempo es una victoria.

Así, pues, ha equipado a sus huestes de una radioenigma de campaña ( es decir, una radio acompañada de una máquina enigma ). Pero, ¿ porqué ha hecho esto ?. Sencillo, en el fragor de la batalla, las comunicaciones directas se hacen complicadas. Es difícil hacer llegar las órdenes, y muchas veces no se tiene claro a quien le estamos dando esas órdenes ( ¿será una vaca?, ¿ será una oveja? ).Conquistar el mundo no es fácil, de hecho muchos lo han intentado pero no lo han conseguido. El profesor Dispar, ha estudiado con detenimiento todas las intentivas anteriores para no reproducir los mismos errores. ¿ Qué pasaría si para dar nuevas órdenes, el profesor necesitase que el sargento vaca tuviese que recorrerse la trinchera indicando una por una a todas las vacas y ovejas que se retirasen ?. Desde que le diese la orden a la primera, hasta que se la diese a la última, pasaría un tiempo precioso. Además, ¿ y si ocurre algo por el camino ?. Todos hemos jugado al fútbol de pequeños después de comer, y sabemos lo que ocurre cuando te pones a correr. Se llama flato. Si nos pasa a nosotros que tenemos un estómago, imaginemos las posibilidades que hay de que le pase al sargento vaca que tiene 4. Las posibilidades de que las órdenes no lleguen a todos los integrantes de nuestra tropa son amplias.

Sabiendo esto, el profesor Dispar, que está loco pero no es idiota, para poder modificar ( actualizar ) fácil y rápidamente el comportamiento de sus huestes, utilizará el patrón Observer.

Como el profesor Dispar no es idiota ( aunque este loco y sea un genio del mal, no es idiota ), ha encontrado una malvada forma de enviar las órdenes a sus tropas. Las ovejas y vacas, estarán equipadas con una radio que sintoniza Cadena Dial ( sí, entendemos que es desagradable, pero dominar el mundo tiene estas cosas ). Sus tropas estarán atentas a la radio, y cuando esta emita una canción de Village People, la máquina enigma les indicará qué acción deben tomar.

enigmaBroadcaster.jpg
Dj Dispar emitiendo su programa

El código será algo así

interface IObserver { public function update( info: Object ): Void; }

interface ISubject { public function addObserver( obs: IObserver ) : Boolean; public function removeObserver( obs: IObserver ) : Boolean; public function notifyObserver( info: Object ) : Void; }

class Cow implements IObserver { function Cow( ) { this.init( ); } private function debug( arg: String ): Void { trace( "Vaca | Cow: ->"+arg ); } private function init( ): Void { debug( "init" ); } public function update( info: Object ) : Void { debug( "La emisora del profesor ha emitido | song in the radio is: "+info.cancion ); debug( enigmaResult( info.cancion ) ); } private function enigmaResult( arg: String ): String { debug( "soy la máquina enigma interpretando qué hacer | I am the enigma machine" ); if( arg != "In the Navy" ) { return "sigo a lo mío. MUUU | nothing to do MUUUUU"; } else { return "cielos, llegó el momento. MUUUUUUUUUUU | Oh, my God, it's the signal MUUU"; } } }

class Sheep implements IObserver { function Sheep( ) { this.init( ); } private function debug( arg: String ): Void { trace( "Oveja | Sheep: ->"+arg ); } private function init( ): Void { debug( "init" ); } public function update( info: Object ) : Void { debug( "La emisora del profesor ha emitido | song in the radio is: "+info.cancion ); debug( enigmaResult( info.cancion ) ); } private function enigmaResult( arg: String ): String { debug( "soy la máquina enigma interpretando qué hacer | I am the enigma machine" ); if( arg != "In the Navy" ) { return "sigo a lo mío. BEEEEE | nothing to do BEEEEEE"; } else { return "cielos, llegó el momento. BEEEE | Oh, my God, it's the signal BEEEEEE"; } } }

class Profesor implements ISubject { private var arrayObservers: Array; function Profesor( ) { init( ); } private function debug( arg: String ) : Void { trace("Profesor: ->"+arg); } private function init( ): Void { debug( "init" ); this.arrayObservers = new Array(); } public function addObserver( obs: IObserver ): Boolean { // Aquí comprobamos que el observer que estamos intentando añadir, no está ya en el array de // observers. En caso que esté, no lo añadiremos y retornaremos false. En caso que no esté, // lo añadimos y retornamos true. debug("registrando"); this.arrayObservers.push( obs ); return true; } public function removeObserver( obs: IObserver ) : Boolean { // comprobamos que el observer a eliminar está en el array. Que no está, retornamos false, que está // lo eliminamos y retornamos true var longObservers: Number = this.arrayObservers.length; for ( var k: Number = 0; k< longObservers; k++ ) { var tmpObserver: IObserver = this.arrayObservers[ k ]; if( tmpObserver == obs ) { this.arrayObservers.splice( k, 1 ); break; } } return true; } public function notifyObserver( info: Object ): Void { var longObservers: Number = this.arrayObservers.length; for ( var k: Number = 0; k

Y en el primer frame del fla

var pd: Profesor = new Profesor(); var miVaca: Cow = new Cow( ); var miOveja: Sheep = new Sheep( ); pd.addObserver( miVaca ); pd.addObserver( miOveja ); var info:Object = new Object(); info.cancion = "In the Navy"; pd.notifyObserver( info );

De este modo, el profesor, puede comunicar rápidamente con todas sus tropas. Es brillante, es genial, es maravilloso, es....¿ perfecto ?. Bueno, la verdad es que está muy bien, pero imaginemos, por un momento una situación en la que una oveja consigue infiltrarse en el alto mando enemigo, y descubre que los enemigos están a punto de ser derrotados, y en ese momento de extasis, la radio emite "In the Navy" indicando a las tropas que hay que retirarse. NOOOOOOOOO ( bueno, esto está traducido, claramente la oveja diría algo como BEEEEEEEEEEE ). Nuestra oveja espía, tiene una información valiosísima, pero que no puede comunicar a su alto mando, porque la forma de transmisión es únicamente de tipo push, del profesor a sus tropas, no hay retorno. Que situación, la oveja necesita transmitir que el ataque debe continuar pero....

enigmaReceiver.jpg
Una oveja tarareando "In the Navy"

ajajajajajajajaj, el profesor Dispar ( loco pero no tonto ) ha pensado en ello, y ha decidido implementar un patrón observer que permita la transmisión tipo push ( del sujeto emisor a todos los sujetos receptores ) y tipo pull ( uno de los objetos receptores, puede solicitar una información al objeto emisor para que este la envíe a todos los demás ). Así, nuestra oveja espía, estará equipada de un número de teléfono al que podrán llamar y solicitar que emitan "Rasputin" de Boney M, y cuando esta canción sea emitida por la radio, las ovejas y vacas que la escuchen, sabrán que ha llegado el momento de la ofensiva final. JAJAJAJAJAJAJAJA. Para ello debemos modificar ligeramente nuestros códigos anteriores.

Para empezar nuestro sujeto, implementará un nuevo método que hemos añadido a la interfaz ISubject( requestInfo ), que permitirá recibir las llamadas telefónicas de la oveja espía.Vemos también que el constructor de las ovejas y las vacas también ha cambiado un poco. Ahora las ovejas y vacas, almacenarán una referencia al constructor. También hemos modificado el código de nuestra oveja ( suponemos que esta es la oveja espía en el momento en que decide que pese a lo que emite la radio, hay que atacar y pide que se emita Rasputin que es otra señal de ataque ).

Por tanto, la interfaz ISubject quedará así:

interface ISubject { public function addObserver( obs: IObserver ) : Boolean; public function removeObserver( obs: IObserver ) : Boolean; public function notifyObserver( info: Object ) : Void; public function requestInfo( arg: String ): Void }

class Cow implements IObserver { private var profesor: Profesor; function Cow( prof: Profesor ) { this.profesor = prof; this.profesor.addObserver(this); this.init( ); } private function debug( arg: String ): Void { trace( "Vaca | Cow: ->"+arg ); } private function init( ): Void { debug( "init" ); } public function update( info: Object ) : Void { debug( "La emisora del profesor ha emitido | song in the radio is: "+info.cancion ); debug( enigmaResult( info.cancion ) ); } private function enigmaResult( arg: String ): String { debug( "soy la máquina enigma interpretando qué hacer | I am the enigma machine" ); if( arg != "In the Navy" && arg!="Rasputin" ) { return "sigo a lo mío. MUUU | nothing to do MUUUUU"; } else { return "cielos, llegó el momento. MUUUUUUUUUUU | Oh, my God, it's the signal MUUU"; } } }

class Sheep implements IObserver { private var profesor: Profesor; function Sheep( prof: Profesor ) { this.profesor = prof; this.profesor.addObserver(this); this.init( ); } private function debug( arg: String ): Void { trace( "Oveja | Sheep: ->"+arg ); } private function init( ): Void { debug( "init" ); } public function update( info: Object ) : Void { debug( "La emisora del profesor ha emitido | song in the radio is: "+info.cancion ); debug( "enigmaResult: "+enigmaResult( info.cancion ) ); } private function enigmaResult( arg: String ): String { debug( "soy la máquina enigma interpretando qué hacer | I am the enigma machine" ); if( arg != "In the Navy" && arg!="Rasputin" ) { debug("un momento, debemos atacar "); debug("wait a moment, we must attack"); debug("pediré un ataque | I'll ask for an attack "); askForAttack( ); return "he solicitado un ataque | i've asked for an attack"; //return "sigo a lo mío. BEEEEE | nothing to do BEEEEEE"; } else { return "cielos, llegó el momento. BEEEE | Oh, my God, it's the signal BEEEEEE"; } } private function askForAttack( ): Void { this.profesor.requestInfo( "Rasputin" ); } }

class Profesor implements ISubject { private var arrayObservers: Array; function Profesor( ) { init( ); } private function debug( arg: String ) : Void { trace("Profesor: ->"+arg); } private function init( ): Void { debug( "init" ); this.arrayObservers = new Array(); } public function addObserver( obs: IObserver ): Boolean { // Aquí comprobamos que el observer que estamos intentando añadir, no está ya en el array de // observers. En caso que esté, no lo añadiremos y retornaremos false. En caso que no esté, // lo añadimos y retornamos true. debug("registrando"); this.arrayObservers.push( obs ); return true; } public function removeObserver( obs: IObserver ) : Boolean { // comprobamos que el observer a eliminar está en el array. Que no está, retornamos false, que está // lo eliminamos y retornamos true var longObservers: Number = this.arrayObservers.length; for ( var k: Number = 0; k< longObservers; k++ ) { var tmpObserver: IObserver = this.arrayObservers[ k ]; if( tmpObserver == obs ) { this.arrayObservers.splice( k, 1 ); break; } } return true; } public function notifyObserver( info: Object ): Void { var longObservers: Number = this.arrayObservers.length; for ( var k: Number = 0; k

Y en el primer frame del fla:

var pd: Profesor = new Profesor(); var miVaca: Cow = new Cow( pd ); var miOveja: Sheep = new Sheep( pd ); var info:Object = new Object(); info.cancion = "Any song"; pd.notifyObserver( info );

Evidentemente, los códigos están simplificados al máximo simplemente para ilustrar como es el patrón. Una vez más comprobamos que el profesor tiene sus planes muy bien estudiados, no es idiota ni tonto. Sólo ¿ está loco ?.....jajajajajajajajajaja ( ya saben, risas de ultratumba con el fundido a negro y el cartel de the end )

Si quieres, puedes bajar el código fuente aquí