« [OT] El nuevo miembro de la familia design-nation | Inicio | Un ejemplo del patrÛn Extension Objects ( la versiÛn Java ) »

Un ejemplo del patrÛn Extension Objects

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

soldierSheep.jpg
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.

peasantSheep.jpg
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:

interface ISubject { public function getExtension( extName: String ): Role; }

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

interface IBasicActions { public function moveArms( ); public function moveLegs( ); public function eat( ); }

Por tanto, la clase Sheep ser·:

class Sheep implements ISubject, IBasicActions { private var soldierRole: SoldierRole; private var peasantRole: PeasantRole; function Sheep( ) { trace( "Soy una oveja" ); } public function getExtension( extName: String ): Role { var returnValue: Role = null; if( extName == "SoldierRole" ) { if( soldierRole == null ) { returnValue = new SoldierRole( this ); } else { returnValue = soldierRole; } } if( extName == "PeasantRole" ) { if( peasantRole == null ) { returnValue = new PeasantRole( this ); } else { returnValue = peasantRole; } } return returnValue; } public function moveArms( ) { // implementaciÛn del movimiento trace( "la oveja mueve un brazo" ); } public function moveLegs( ) { // implementaciÛn del movimiento trace( "la oveja mueve una pierna" ); } public function eat( ) { // implementaciÛn del sistema de alimentaciÛn trace( "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Ì )

class Role { function Role( ) { } }

Por tanto, el rol de soldado:

class SoldierRole extends Role implements ISoldierActions { private var subject: IBasicActions; function SoldierRole( subject: IBasicActions ) { this.subject = subject; trace( "Se crea el rol de soldado" ); } public function destroy( ) { //Comportamiento especÌfico trace( "Soldier interface. Destruye!" ); //Utiliza alguno de los mÈtodos del animal subject.eat( ); } public function moveTo( ) { //Comportamiento especÌfico trace( "Soldier Interface. moveTo" ); //Utiliza alguno de los mÈtodos del animal subject.moveLegs( ); } }

Y el rol de campesino:

class PeasantRole extends Role implements IPeasantActions { private var subject: IBasicActions ; function PeasantRole( subject: IBasicActions ) { this.subject = subject; trace( "Se crea el rol de campesino" ); } public function driveTo( ) { //Comportamiento especÌfico trace( "Lo llevo " ); //Utiliza alguno de los mÈtodos del animal subject.moveArms( ); subject.moveLegs( ); } public function doGardening( ) { //Comportamiento especÌfico trace( "Vale, cuido el jardÌn" ); //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:

var sheep: Sheep = new Sheep( ); var soldierSheep: ISoldierActions = ISoldierActions( sheep.getExtension( "SoldierRole" ) ); soldierSheep.destroy( ); var peasantSheep: IPeasantActions = 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:

interface ISubject { public function getExtension( extName: String ): Role; public function addExtension( extName: String , extension: Role ); public function removeExtension( extName: String ); }

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

class Sheep implements ISubject, IBasicActions { private var rolesCol: Object; function Sheep( ) { trace( "I'm a sheep" ); rolesCol = new Object( ); } public function getExtension( extName: String ): Role { return rolesCol[ extName ]; } public function addExtension( extName: String, extension: Role ) { rolesCol[ extName ] = extension; } public function removeExtension( extName: String ) { rolesCol[ extName ] = null; } public function moveArms( ) { // movement implementation trace( "the sheep moves one arm" ); } public function moveLegs( ) { // movement implementation trace( "the sheep moves one leg" ); } public function eat( ) { // implements the way sheeps eat trace( "munch. munch" ); } }

Y el Profesor Dispar puede hacer algo como:

var sheep: Sheep = new Sheep( ); sheep.addExtension( "SoldierRole", new SoldierRole( sheep ) ); var soldierSheep: ISoldierActions = ISoldierActions( sheep.getExtension( "SoldierRole" ) ); soldierSheep.destroy(); sheep.removeExtension( "SoldierRole" ); sheep.addExtension( "PeasantRole", new PeasantRole( sheep ) ); var peasantSheep: IPeasantActions = 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

TrackBack

URL del Trackback para esta entrada:
http://ctarda.dreamhosters.com/cgi-bin/mt-tb.cgi/679