øRecuerdas al Professor Dispar?. øRecuerdas sus malvados planes para dominar el mundo?.
Hoy veremos cÛmo el patrÛn Extension Objects ( o "cÛmo cambiar el interfaz que implementa una clase en tiempo de ejecuciÛn" ) ha ayudado al Profesor Dispar. Pero no va a ser una tarea f·cil, porque este patrÛn es muy complejo, pero øquiÈn dijo que ser un genio del mal fuera f·cil?.
En el anterior episodio, el Profesor Dispar diseÒÛ una m·quina para clonar que era capaz de clonar cualquier animal, sin conocer su raza, credo o religiÛn, delegando los detalles del proceso de clonaciÛn en el propio animal.
Pues aquÌ empieza el episodio de hoy. El Profesor Dispar ha clonado ya nueve diez mil ovejas y nueve mil vacas ( dijo algo asÌ como "Èse el n˙mero exacto que necesito, juajuajuajajajajajaaaaaaa" ). Pero de repento, se diÛ cuenta que sÌ, va a necesitar muchas ovejas, y sÌ, va a necesitar muchas vacas, pero no todas las ovejas ( ni todas las vacas ) deber·n tener el mismo rol. øPor quÈ?. Bueno, dominar el mundo no es f·cil. Hace falta organizaciÛn. °Hace falta un ( malvado ) plan!.
Y el Profesor Dispar tiene un plan. Quiere entrenar a algunas de sus ovejas y de sus vacas como soldados. Quiere que sean m·quinas de matar inmisericordes ( perdÛn por la violencia, pero es lo que tiene ser un genio loco ). Pero tambiÈn quiere que algunas de las ovejas y algunas de las vacas trabajen en las f·bricas haciendo armas. Y tambiÈn necesitan que algunas ovejas y algunas vacas trabajen como granjeras, cultivando los alimentos de sus compaÒeras militares ( recuerda, el Profesor Dispar est· loco, pero no es idiota ). Por tanto, se puede decir que lo que necesita es poder asignar distintos roles a los clones que ha creado.
Una oveja soldado. Sin comentarios.
[Nota]. Con el fin de mantener este tinglado un poco organizado, con un ˙nico patrÛn por post, escribirÈ las clases Sheep y Cow desde el principio. Pero el Profesor Dispar no lo har· asÌ. …l aÒadir· el cÛdigo del episodio de hoy al que ya tenÌa.
Bien, pues el genio del mal empieza a pensar. "Muy bien, tengo una clase que representa una oveja ( Sheep ). Y ahora necesito tener una oveja que se comporte como un soldado, y otra que se comporte como una campesina, y lo mismo con las vacas". Eso suena a "extender la funcionalidad de una clase", øno?. °Brillante!! "EscribirÈ una clase SoldierSheep ( oveja soldado ) que extienda a Sheep, y otra clase, PeasantSheep ( oveja campesina ), que tambiÈn extienda a Sheep". ( InsÈrtense cinco minutos de risas histÈricas en este punto, por favor ).
Pero antes de ponerse a escribir cÛdigo, el Profesor Dispar se da cuenta que probablemente Èsa no sea la mejor soluciÛn, porque cada vez que quiera asignar un nuevo rol a una oveja, tendr· que escribir una nueva subclase ( por ejemplo, una OvejaIngeniera, o una OvejaMaitre... ). Y adem·s, Èl no sabe a priori cu·ntos roles va a necesitar asignar a una oveja. Todo depende de lo que necesite en el futuro. Necesita dar soporte a la adiciÛn de interfaces desconocidos a la clase Sheep ( øno es un genio? )
TambiÈn se da cuenta que su primera forma de atacar el problema tiene otro punto dÈbil, y es que no podr· re-asignar un rol. Si crea una OvejaSoldado, tendr· una OvejaSoldado para siempre, aunque la necesite para cultivar cebollas ( recuerda, el Profesor est· loco, a veces tiene unas ideas muy extraÒas )
Pero tambiÈn se da cuenta de otro punto dÈbil, Èste mucho m·s sutil. Una OvejaSoldado y una Oveja son exactamente lo mismo. La ˙nica diferencia es que una de ellas tiene un comportamiento particular, pero en esencia, son lo mismo. Por tanto, no es muy apropiado representarlas utilizando clases distintas.
El Profesor Dispar est· loco, pero no es idiota ( sÌ, ya sÈ, ya sÈ que ya lo sabes ). Por tanto, decide que ha encontrado demasiados puntos dÈbiles para su idea inicial, y que por tanto, es el momento de acudir a google en busca de una soluciÛn.
Una oveja campesina. Lo creas o no, su hobby es la jardinerÌa.
Tras varias b˙squedas fallidas, recuerda un libro (Pattern Languages of Program Design, Volume 3, Addison-Wesley, 1998. ), en el que Erich Gamma ( sÌ, uno de los autores del GoF ) escribiÛ un capÌtulo sobre el patrÛn Extension Objects.
Este patrÛn intenta anticipar la extensiÛn del interfaz de un objeto en el futuro ( eso quiere decir: se pueden aÒadir interfaces a una clase en tiempo de ejecuciÛn ).
AsÌ que empieza a leer, y a reirse. Y cuanto m·s lee, m·s se rÌe.
La idea es muy sencilla. Una clase ( Sheep ) ser· capaz de cambiar el interfaz que implementa en tiempo de ejecuciÛn, seleccionando ese interfaz de entre una colecciÛn de objetos ( los Extension Objects ). Cada uno de esos objetos encapsular· uno de los roles ( SoldierSheep, PeasantSheep,... ) ( A estas alturas, el Profesor Dispar ya lleva casi veinte minutos de risas histÈricas!! )
Pero øcÛmo ser· capaz la clase Sheep de seleccionar el interfaz a implementar? ø Y la clase Cow?. Bien, ambas clases implementar·n el siguiente interfaz:
public interface ISubject
{
public Role getExtension( String extName );
}
La clase Sheep implementa un interfaz para encapsular las acciones que implementa por ser un animal ( comer, mover las piernas, los brazos... perdÛn: las patas, las patas... )
public interface IBasicActions
{
public void moveArms( );
public void moveLegs( );
public void eat( );
}
Por tanto, la clase Sheep ser·:
public class Sheep implements ISubject, IBasicActions
{
private SoldierRole soldierRole;
private PeasantRole peasantRole;
public Sheep( )
{
System.out.println( "I'm a sheep" );
}
public Role getExtension( String extName )
{
Role returnValue = null;
if( extName.equals( "SoldierRole" ) )
{
if( soldierRole == null )
{
returnValue = new SoldierRole( this );
}
else
{
returnValue = soldierRole;
}
}
if( extName.equals( "PeasantRole" ) )
{
if( peasantRole == null )
{
returnValue = new PeasantRole( this );
}
else
{
returnValue = peasantRole;
}
}
return returnValue;
}
public void moveArms( )
{
// implementaciÛn del movimiento
System.out.println( "la oveja mueve un brazo" );
}
public void moveLegs( )
{
System.out.println( "mueve una pierna" );
}
public void eat( )
{
System.out.println( "munch. munch" );
}
}
FÌjate en cÛmo esa clase implementa el mÈtodo getExtension, que elige la clase que debe devolver de entre una colecciÛn de roles ( que son variables de la clase ). ø Y los roles ?
AquÌ est· el rol base ( no he implementado nada en Èl, pero cualquier funcionalidad com˙n a los roles deberÌa implementarse aquÌ )
public abstract class Role
{
}
Por tanto, el rol de soldado:
So, the SoldierRole:
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( )
{
//Comportamiento especÌfico
System.out.println( "Soldier interface. destroy" );
//Utiliza alguno de los mÈtodos del animal
subject.eat( );
}
public void moveTo( )
{
//Comportamiento especÌfico
System.out.println( "Soldier Interface. moveTo" );
//Utiliza alguno de los mÈtodos del animal
subject.moveLegs( );
}
}
Y el rol de campesino:
public class PeasantRole extends Role implements IPeasantActions
{
private IBasicActions subject;
public PeasantRole( IBasicActions subject )
{
this.subject = subject;
System.out.println( "PeasantBehaviour creado" );
}
public void driveTo( )
{
//Comportamiento especÌfico
System.out.println( "Ya voy" );
//Utiliza alguno de los mÈtodos del animal
subject.moveArms( );
subject.moveLegs( );
}
public void doGardening( )
{
//Comportamiento especÌfico
System.out.println( "OK, gardening" );
//Utiliza alguno de los mÈtodos del animal
subject.moveArms( );
}
}
Ambas clases ( SoldierRole y PeasantRole ) implementan, respectivamente, los interfaces ISoldierActions e IPeasantActions. Esos son los interfaces que parecer· que implementa la clase Sheep
Mira:
public class ProfessorCoupling
{
public static void main( String[ ] args )
{
Sheep sheep = new Sheep( );
ISoldierActions soldierSheep = ( ISoldierActions ) sheep.getExtension( "SoldierRole" );
soldierSheep.destroy( );
IPeasantActions peasantSheep = ( IPeasantActions ) sheep.getExtension( "PeasantRole" );
peasantSheep.doGardening( );
}
}
( InsÈrtense treinta minutos de risas histÈricas aquÌ ). Pero, un momento!!!. El Profesor Dispar se acaba de dar cuenta de un pequeÒo problema! ( bueno, en realidad no es un problemas, es una soluciÛn difÌcil )
La clase Sheep tiene conocimiento de sus extensiones ( los roles ). Esas extensiones se guardan en variables de clase, asÌ que no hay forma de aÒadir alg˙n rol, o modificar los ya existentes en tiempo de ejecuciÛn
Por tanto, el Profesor decide refactorizar la clase Sheep. Esta clase ya no guardar· sus posibles extensiones en variables de clase, sino que manejar· una colecciÛn ( un HashMap, un objeto ) con esos roles. De esta forma, ser· posible aÒadir o eliminar extensiones ( roles ) cuando se necesite ( no te creerÌas cÛmo se est· riendo ahora ).
El nuevo interfaz ISubject:
public interface ISubject
{
public Role getExtension( String extName );
public void addExtension( String extName, Role extension );
public void removeExtension( String extName );
}
Y la nueva clase Sheep ( no me gusta un pelo la forma en la que el Profesor ha implementado la colecciÛn, utilizando un objeto, pero bueno... )
import java.util.HashMap;
public class Sheep implements ISubject, IBasicActions
{
private HashMap rolesCol;
public Sheep( )
{
System.out.println( "I'm a sheep" );
rolesCol = new HashMap( );
}
public Role getExtension( String extName )
{
return ( Role ) rolesCol.get( extName );
}
public void addExtension( String extName, Role extension )
{
rolesCol.put( extName, extension );
}
public void removeExtension( String extName )
{
rolesCol.remove( extName );
}
public void moveArms( )
{
System.out.println( "mueve un brazo" );
}
public void moveLegs( )
{
System.out.println( "la oveja mueve una pierna" );
}
public void eat( )
{
// implementaciÛn del sistema de alimentaciÛn
System.out.println( "munch. munch" );
}
}
Y el Profesor Dispar puede hacer algo como:
public class ProfessorCoupling
{
public static void main( String[ ] args )
{
Sheep sheep = new Sheep( );
sheep.addExtension( "SoldierRole", new SoldierRole( sheep ) );
ISoldierActions soldierSheep = ( ISoldierActions ) sheep.getExtension( "SoldierRole" );
soldierSheep.destroy();
sheep.removeExtension( "SoldierRole" );
sheep.addExtension( "PeasantRole", new PeasantRole( sheep ) );
IPeasantActions peasantSheep = ( IPeasantActions ) sheep.getExtension( "PeasantRole" );
peasantSheep.doGardening( );
}
}
Por tanto, los clones creados por la m·quina para clonar pueden cambiar su aspecto en tiempo de ejecuciÛn ( primero pueden ser soldados, luego campesinos, m·s tarde lo que al Profesor le dÈ la gana ). El profesor ha conseguido mantener la abstracciÛn Oveja limpia de operaciones especÌficas para un cliente. Eso tambiÈn lo podrÌa haber conseguido gracias a la herencia, definiendo las operaciones para los clientes en las subclases, pero eso habrÌa generado una jerarquÌ difÌcil de manejar
La siguiente aventura del Profesor Dispar ser· m·s f·cil, lo prometo. °Incluso los genios del mal necesitan vacaciones!
[Nota importante]. Auto crÌtica: El cÛdigo para recuperar el rol activo no pasarÌa ning˙n control de calidad. DeberÌa implementarse de otra forma, el uso de una cadena de texto como clave es bastante peligroso