Un ejemplo del patron Command
Todo está preparado. Las ovejas y las vacas han sido clonadas y sus roles han sido asignados. Es el momento perfecto para que el Profesor Dispar lance su ataque final. ¡¡¡¡¡Ha llegado el momento de conquistar el mundo!!!!
¿Pero cómo dará el Profesor Dispar a sus tropas la orden de atacar?
Si recuerdas los posts anteriores, El Profesor Dispar ha conseguido clonar cualquier animal, y después, ha conseguido asignarles cualquier rol de forma dinámica. Así pues, el Profesor ha clonado ovejas y vacas, y dispone de un ejército de ovejas soldado, ovejas campesinas, vacas soldado y vacas campesinas ( es que no es fácil dominar el mundo )
El Profesor Dispar está loco, pero no es idiota. Quiere conquistar el mundo, cierto, tiene un malvado plan para hacerlo, cierto, pero sabe que tener un buen plan ( aunque sea malvado ) no es una garantía para el éxito. Necesita un plan de emergencia.
El Profesor quiere que algunas de sus ovjeas soldado participen en el primer ( y por tanto, glorioso ) ataque. Pero también quiere reservar unas cuantas de sus ovejas soldado y dejarlas descansando mientras sus colegas son masacradas en el campo de batalla ( perdón por el exceso de violencia, pero, ya se sabe, dominar el mundo es algo para lo que hace falta estómago ), y que puedan ser utilizadas como refuerzos.
Pero, ¿cómo podrá hacerlo?. El Profesor es un hombre muuuuuy ocupado, por lo tanto el ataque debe poderse lanzar sin mucha intervención por su parte. Algo tan simple como apretar el botón de "dominar el mundo" sería perfecto. Es rápido, es fácil, y el Profesor puede delegar la tarea en alguno de sus subordinados ( muhahahahahahahahaaa ). Eso sería perfecto, pero para conseguirlo tiene que encontrar la forma de poderle decir a cada oveja que es lo que se supone que debe hacer cuando el "glorioso momento del ataque" llegue.
¿Pero cómo ?. El conocimiento que tiene el Profesor de las ovjeas soldado es su interfaz. El Profesor sabe que cada oveja soldado implementa el interfaz ISoldierActions ( por favor, si no lo has leído, revisa el post sobre el patrón extension objects, porque voy a utilizar parte del código de ese post ). Resumiendo, lo que en realidad necesita es que algunas ovejas ejecuten uno de los métodos de ISoldierActions, y otras ovejas ejecuten uno distinto cuando reciban la orden de atacar.
Intentemos entenderlo con un ejemplo. Aquí están el interfaz ISoldierActions, y la clase SoldierRole ( un poco diferentes de las que utilizamos en el post del extension objects):
interface ISoldierActions
{
public function destroy( );
public function moveTo( );
public function waitForMoreOrders( );
}
class SoldierRole extends Role implements ISoldierActions
{
private var subject: IBasicActions;
function SoldierRole( subject: IBasicActions )
{
this.subject = subject;
trace( "SoliderBehaviour created" );
}
public function destroy( )
{
//Specific behaviour
trace( "Soldier interface. destroy" );
//Uses some of the animal's methods
subject.eat( );
}
public function moveTo( )
{
//Specific behaviour
trace( "Soldier Interface. moveTo" );
//Uses some of the animal's methods
subject.moveLegs( );
}
public function waitForMoreOrders( )
{
trace( "Beeeee, I'll wait for more orders" );
}
}
Tanto el interfaz IPeasantActions como la clase PeasantRole no han cambiado
El Profesor Dispar ha clonado diez mil ovejas soldado, y diez mil ovejas campesinas, y utiliza dos arrays para almacenar una referencia a todas ellas.
Por tanto, al apretar el botón de "al ataqueeeeeeeeeeeeeee" podría hacer algo así:
class ProfesorDispar
{
public static function attack( soldiers: Array, peasants: Array )
{
var numSoldiers: Number = soldiers.length;
var numPeasants: Number = peasants.length;
for( var i=0; i< numSoldiers / 2; i++ )
{
soldiers[ i ].destroy( );
}
for( var i=numSoldiers/2; i< numSoldiers; i++ )
{
soldiers[ i ].waitForMoreOrders( );
}
for( var i=0; i< numPeasants / 2; i++ )
{
peasants[ i ].doGardening( );
}
for( var i=numPeasants/2; i< numPeasants; i++ )
{
peasants[ i ].driveTo( );
}
}
}
Y en la línea de tiempo principal:
var soldiers: Array = new Array( );
var peasants: Array = new Array( );
for( var i=0; i<10; i++ )
{
var sheep: Sheep = new Sheep( );
sheep.addExtension( "SoldierRole", new SoldierRole( sheep ) );
var soldierSheep: ISoldierActions = ISoldierActions( sheep.getExtension( "SoldierRole" ) );
soldiers.push( soldierSheep );
}
for( var i=10; i<20; i++ )
{
var sheep: Sheep = new Sheep( );
sheep.addExtension( "PeasantRole", new PeasantRole( sheep ) );
var peasantSheep: IPeasantActions = IPeasantActions( sheep.getExtension( "PeasantRole" ) );
peasants.push( peasantSheep );
}
ProfesorDispar.attack( soldiers, peasants );
( Insértense varios minutos de risas histéricas aquí, por favor ). Ya sabes lo que viene ahora, n o?. Exacto. El Profesor Dispar está loco, pero no es un idiota. No le gusta la solución que ha encontrado. ¿Por qué?. Bueno, no es exactamente lo que quería. El Profesor simplemente quiere poder decir "al ataqueeeeeeeee", y comenzar a reirse histéricamente mientras las ovejas se lanzan ciegamente a cumplir sus órdenes.
El Profesor barrunta que todo podría ser mucho más fácil si pudiera darle a cada una de las ovejas un sobre conteniendo sus órdenes. Cuando el "momento de gloria" llegue, simplemente le tendrá que decir a cada oveja que habra el sobre y obedezca las órdenes contenidas en él. Pero no quiere saber de qué forma tienen que cumplir esas órdenes las ovejas, y de hecho, ni siquiera quiere saber si le está mandando algo a una oveja o a una vaca, o a lo que sea.
Pero justo en ese momento, el Profesor recuerda sus años mozos, cuando era estudiante, y entre partida y partida de tute leyó el GoF. Y entonces, comienza a reírse con más fuerza que nunca. Porque ha recordado el patrón Command.
El Profesor quiere dar cuatro órdenes diferentes. Dos para ser obedecidas por las ovejas soldado ( "ataca", y "espera hasta recibir nuevas órdenes" ), y las otras dos dirigidas a las ovejas campesinas ( "comience a trabajar en el jardín", y "lleva el tractor a casa" ).
Por tanto, va a encapsular la orden, y el receptor de dicha orden, en un paquete ( el sobre ). ¿Cómo?. Mira:
En primer lugar, escribirá el siguiente interfaz:
interface ICommandActions
{
public function execute( );
}
Y los distintos comandos serán:
class SoldierAttack implements ICommandActions
{
private var receiver: ISoldierActions;
public function SoldierAttack( soldier: ISoldierActions )
{
this.receiver = soldier;
}
public function execute( )
{
this.receiver.destroy( );
}
}
class SoldierWait implements ICommandActions
{
private var receiver: ISoldierActions;
public function SoldierWait( soldier: ISoldierActions )
{
this.receiver = soldier;
}
public function execute( )
{
this.receiver.waitForMoreOrders( );
}
}
class PeasantAttack implements ICommandActions
{
private var receiver: IPeasantActions;
function PeasantAttack( peasant: IPeasantActions )
{
this.receiver = peasant;
}
public function execute( )
{
receiver.doGardening( );
}
}
class PeasantDrive implements ICommandActions
{
private var receiver: IPeasantActions;
function PeasantDrive( peasant: IPeasantActions )
{
this.receiver = peasant;
}
public function execute( )
{
receiver.driveTo( );
}
}
Por tanto, cuando el Profesor presione el botón de "al ataqueeeeeeeeee", tendrá que hacer algo así:
class ProfesorDispar
{
public static function attack( commandsArray: Array )
{
for( var k=0; k< commandsArray.length; k++ )
{
commandsArray[ k ].execute( );
}
}
}
Ese método recibe como parámetro un array conteniendo todos los comandos. Ese array se podría construir con código similar a éste:
var theArmy: Array = new Array( );
for( var i=0; i<5; i++ )
{
var sheep: Sheep = new Sheep( );
sheep.addExtension( "SoldierRole", new SoldierRole( sheep ) );
var soldierSheep: ISoldierActions = ISoldierActions( sheep.getExtension( "SoldierRole" ) );
var saCommand: SoldierAttack = new SoldierAttack( soldierSheep );
theArmy.push( saCommand );
}
for( var i=5; i<10; i++ )
{
var sheep: Sheep = new Sheep( );
sheep.addExtension( "SoldierRole", new SoldierRole( sheep ) );
var soldierSheep: ISoldierActions = ISoldierActions( sheep.getExtension( "SoldierRole" ) );
var swCommand: SoldierWait = new SoldierWait( soldierSheep );
theArmy.push( swCommand );
}
for( var i=10; i<15; i++ )
{
var sheep: Sheep = new Sheep( );
sheep.addExtension( "PeasantRole", new PeasantRole( sheep ) );
var peasantSheep: IPeasantActions = IPeasantActions( sheep.getExtension( "PeasantRole" ) );
var paCommand: PeasantAttack = new PeasantAttack( peasantSheep );
theArmy.push( paCommand );
}
for( var i=15; i<20; i++ )
{
var sheep: Sheep = new Sheep( );
sheep.addExtension( "PeasantRole", new PeasantRole( sheep ) );
var peasantSheep: IPeasantActions = IPeasantActions( sheep.getExtension( "PeasantRole" ) );
var pdCommand: PeasantDrive = new PeasantDrive( peasantSheep );
theArmy.push( pdCommand );
}
ProfesorDispar.attack( theArmy );
El código es horrible, pero sirve para ilustrar cómo el Profesor ha creado una colección de objetos, cada uno de los cuales encapsula un comando y el recpetor de dicho comando. Ahora, el Profesor Dispar no necesita saber nada acerca de esos comandos, ni del receptor de los mismo. Simplemente tiene que decir "eh, comando, ejecútese", y el comando hará el resto.
De hecho, el Profesor Dispar ha conseguido desacoplar tres procesos diferentes: la creación de los objetos ( implementando el patrón prototype ), la asignación de roles a esos objetos ( con el patrón extension objects ), y la forma en que esos roles pasan a la acción ( el patrón command )
Al final va a resultar que sí es un genio...
Comentarios
me queda un mal sabor de boca con el pattern prototype , creo q seria interesante (no se si correcto) q el metodo clone clonara tambien sus propiedades , quedaria chulo una cosa como :
var templateSheep = new Sheep()
templateSheep.addExtension( "SoldierRole", new SoldierRole());
ProfesorDispar.createGrupo("grupoSheepSoldier",20,templateSheep);
ProfesorDispar.setCommandGrupo("grupoSheepSoldier",new SoldierAttack());
ProfesorDispar.attack("grupoSheepSoldier");//sin argumento q llame a todos los grupos (es un vago!)
Obviamente las subClases Role deberia implementar ICloneable y en Sheep :
public function clone( ): ICloneable
{
trace( "Beeee, soy un nuevo clon de Dolly" );
var sheep = new Sheep();
for (var i in rolesCol){
var rol:ICloneable = rolesCol[i].clone();
sheep.addExtension(i,rol);
}
return sheep;
}
Gracias por regalarnos estos patterns , de verdad es un placer asimilar patterns de esta forma tan original.
salu2
Publicado por: buho29 | Abril 28, 2005 12:34 PM
Efectivamente!.
La solución que aportas sería perfecta para clonar animales con todas sus colecciones de roles ya disponibles.
En cuanto a la corrección de la solución, a mí me parece perfectamente válida, porque el conocimiento de cómo es una oveja reside en la oveja, y es ella por tanto la que sabe cómo debe producir una copia de sí misma.
Publicado por: Cesar Tardaguila | Abril 28, 2005 12:47 PM