Abril 26, 2005

Un ejemplo del patron Command ( la versión Java )

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 poder ser lanzado sin mucha intervenvió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):

public interface ISoldierActions
{
 public void destroy( );
 public void moveTo( );
 public void waitForMoreOrders( );
}
public class SoldierRole extends Role implements ISoldierActions
{
 private IBasicActions subject;
     
 public SoldierRole( IBasicActions subject )
 {
  this.subject = subject;
          
  System.out.println( "SoliderBehaviour created" );
 }
 
 public void destroy( )
 {
  //Specific behaviour
  System.out.println( "Soldier interface. destroy" );
          
  //Use some of the animal's methods
  subject.eat( );
 }
 
 public void moveTo( )
 {
  //Specific behaviour
  System.out.println( "Soldier Interface. moveTo" );
          
  //Use some of the animal's methods
  subject.moveLegs( );
 }
     
 public void waitForMoreOrders( )
 {
  System.out.println( "I'll wait for more orders. Beeeeeee" );
 }
 
}

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 ProfesorDispar( )
 {
 }
 
 public void attack( ISoldierActions[] soldiers, IPeasantActions[] peasants )
 {
  int soldiersCount = soldiers.length;
  int peasantsCount = peasants.length;
          
  for( int idx=0; idx< soldiersCount / 2; idx++ )
  {
   soldiers[ idx ].destroy( );
  }
          
  for( int idx=soldiersCount/2; idx< soldiersCount; idx++ )
  {
   soldiers[ idx ].waitForMoreOrders( );
  }
          
  for( int idx=0; idx< peasantsCount / 2; idx++ )
  {
   peasants[ idx ].doGardening( );
  }
          
  for( int idx=peasantsCount/2; idx< peasantsCount; idx++ )
  {
   peasants[ idx ].driveTo( );
  }        
 }
}

( 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.

oveja_sobre.jpg

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:

public interface ICommandActions
{
 public void execute( );
}

Y los distintos comandos serán:

public class SoldierAttack implements ICommandActions
{
 ISoldierActions receiver;
     
 public SoldierAttack( ISoldierActions soldier )
 {
  receiver = soldier;
 }
     
 public void execute(  )
 {
  receiver.destroy( );
 }
}
public class SoldierWait implements ICommandActions
{
 ISoldierActions receiver;
     
 public SoldierWait( ISoldierActions soldier )
 {
  receiver = soldier;
 }
 
 public void execute( )
 {
  receiver.waitForMoreOrders( );
 }
 
}
public class PeasantAttack implements ICommandActions
{
 IPeasantActions receiver;
     
 public PeasantAttack( IPeasantActions peasant )
 {
  receiver = peasant;
 }
 
 public void execute( )
 {
  receiver.doGardening( );
 }
}
public class PeasantDrive implements ICommandActions
{
 private IPeasantActions receiver;
 
 public PeasantDrive( IPeasantActions peasant )
 {
  receiver = peasant;
 }
     
 public void execute( )
 {
  receiver.driveTo( );
 }
}

Por tanto, cuando el Profesor presione el botón de "al ataqueeeeeeeeee", tendrá que hacer algo así:

public void attack( ICommandActions[] theArmy )
{
 int sheepsCount = theArmy.length;
         
 for( int idx=0; idx< sheepsCount; idx++ )
 {
  theArmy[ idx ].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:

public class ProfesorDispar
{
 
 public ProfesorDispar( )
 {
 }
     
 public void attack( ICommandActions[] theArmy )
 {
  int sheepsCount = theArmy.length;
          
  for( int idx=0; idx< sheepsCount; idx++ )
   {
   theArmy[ idx ].execute( );
  }
 }
 
 public static void main( String[ ] args )
 {
          
  Sheep sheep = null;
          
  ICommandActions[ ] theArmy = new ICommandActions[ 20 ];
          
  ISoldierActions soldierSheep = null;
  IPeasantActions peasantSheep = null;
          
  SoldierAttack saCommand = null;
  SoldierWait swCommand = null;
          
  PeasantAttack paCommand = null;
  PeasantDrive pdCommand = null;
          
  for( int i=0; i<5; i++ )
  {
   sheep = new Sheep( );
               
   sheep.addExtension( "SoldierRole", new SoldierRole( sheep ) );
               
   soldierSheep = ( ISoldierActions ) sheep.getExtension( "SoldierRole" );
               
   saCommand = new SoldierAttack( soldierSheep );
               
   theArmy[ i ] = saCommand;
  }
          
  for( int i=5; i<10; i++ )
  {
   sheep = new Sheep( );
               
   sheep.addExtension( "SoldierRole", new SoldierRole( sheep ) );
               
   soldierSheep = ( ISoldierActions ) sheep.getExtension( "SoldierRole" );
               
   swCommand = new SoldierWait( soldierSheep );
               
   theArmy[ i ] = swCommand;
  }
          
  for( int i=10; i<15; i++ )
  {
   sheep = new Sheep( );
               
   sheep.addExtension( "PeasantRole", new PeasantRole( sheep ) );
               
   peasantSheep =  ( IPeasantActions ) sheep.getExtension( "PeasantRole" );
               
   paCommand = new PeasantAttack( peasantSheep );
               
   theArmy[ i ] = paCommand;
  }
          
  for( int i=15; i<20; i++ )
  {
   sheep = new Sheep( );
               
   sheep.addExtension( "PeasantRole", new PeasantRole( sheep ) );
               
   peasantSheep =  ( IPeasantActions ) sheep.getExtension( "PeasantRole" );
               
   pdCommand = new PeasantDrive( peasantSheep );
               
   theArmy[ i ] = pdCommand;
  }        
          
  ProfesorDispar profesor = new ProfesorDispar( );
          
  profesor.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...

Escrito por Cesar Tardaguila en: Abril 26, 2005 07:00 AM | TrackBack
Comentarios