« Septiembre 2005 | Inicio | Noviembre 2005 »

Octubre 18, 2005

Por cierto...

El Studio 8 de Macromedia ya est· disponible en castellano. Y sÌ, hay licencia para profesores y estudiantes por 85Ä m·s IVA.

Macromedia EspaÒa

Y el patrón era...

Ayer preguntaba por el nombre de un patrón.

El patrón era el Strategy.

Octubre 17, 2005

Un ejemplo del patrÛn... °adivÌnalo!

EDITADO: El patrÛn es el Strategy. Por tanto, el tÌtulo del post deberÌa ser "Un ejemplo el patrÛn Strategy en actionscript"

El Profesor Dispar ha estado disfrutando de unas merecidas vacaciones tras conquistar el mundo (sÌ, desde la ˙ltima vez que tuvimos noticias de Èl, ha conseguido llevar a buen puerto sus malÈficos planes).

Pero el dÌa a dÌa de dominar el mundo le est· matando de aburrimiento. El Profesor Dispar echa de menos los viejos tiempos, cuando nadie le comprendÌa, cuando podÌa odiar a todos los lÌderes del mundo porque le ignoraban... Ahora pasa la mayor parte del dÌa haciendo papeleo, y aÒora los dÌas en los que se podÌa dar una vuelta por los campamentos de sus tropas y confraternizar con ellos, contar chistes, tomar unas cervecitas...

AsÌ que, en un arranque de genio (malvado, claro), ha decidido que, para matar el aburrimiento, quiere ver un desfile cada dÌa. Mejor dicho, un desfile diferente cada dÌa. Un dÌa le pedir· a su Mariscal que le prepare un desfile con los chicos de la compaÒÌa B, otro dÌa querr· que desfilen los soldados cuyo nombre contenga una P... ahhh, los genios del mal...

Las tropas del Profesor Dispar est·n bien entrenadas. Saben que, en caso de ser capturadas, sÛlo deben decir su nombre, compaÒÌa, y n˙mero de serie. Nada m·s. AsÌ que podrÌamos abstraer un soldado como algo parecido a esto:

class Soldier { private var name : String; private var id : String; private var company : String; private var weapons : Object; function Soldier( initInfo: Object ) { this.name = initInfo.name; this.id = initInfo.id; this.company= initInfo.company; } public function getPublicInfo( ) { return { name: this.name, id: this.id, company: this.company }; } //Other methods public function toString( ): String { return "soldier{ name: " + this.name + ", id: " + this.id + ", company: " + this.company + " }"; } }

Lo que define, pues, a un soldado es su nombre, su compaÒÌa, y su n˙mero de serie, y esa es la ˙nica informaciÛn que proporcionar·n cuando se les pregunte.

Imagina que el Profesor Dispar le pide al Mariscal que prepare un desfile con los soldados de la compaÒÌa B. El Mariscal, entre cuyas responsabilidades est·n jugar al golf y mantener en forma a sus soldados, tendr· que ir preguntando a los soldados a quÈ compaÒÌa pertenecen, y seleccionar·, por tanto sÛlo a aquellos que sirvan en la compaÒÌa B. Pero ahora, imagina que el Profesor quiere que desfilen sÛlo los soldados en cuyo nombre haya una letra "P". Entonces, el cÛdigo del Mariscal ser· algo asÌ:

class Marshal { public static var CONTAINS_LETTER: String = "Letter"; public static var COMPANY : String = "Company"; private var soldiersList: Array; function Marshal( ) { this.soldiersList = new Array( ); } public function recruitSoldier( newSoldier: Soldier ) { this.soldiersList.push( newSoldier ); } public function getFilteredCollection( filterType: String, filterValue: String ): Array { var returnVal: Array = new Array( ); var numSoldiers: Number = this.soldiersList.length; var actualSoldierInfo: Object; for( var k: Number = 0; k< numSoldiers; k++ ) { actualSoldierInfo = this.soldiersList[ k ].getPublicInfo( ); switch( filterType ) { case Marshal.CONTAINS_LETTER: if( actualSoldierInfo.name.indexOf( filterValue ) != -1 ) { returnVal.push( this.soldiersList[ k ] ); } break; case Marshal.COMPANY: if( actualSoldierInfo.company == filterValue ) { returnVal.push( this.soldiersList[ k ] ); } break; default: trace( "Filter not created" ); } } return returnVal; } }

Y el Profesor har· algo como esto:

class Professor { function Professor( ) { } public static function initApp( ) { var professor : Professor = new Professor( ); var marshal : Marshal = new Marshal( ); marshal.recruitSoldier( new Soldier( { name: "Peter", id: "0001", company: "B" } ) ); marshal.recruitSoldier( new Soldier( { name: "Paul", id: "0002", company: "B" } ) ); marshal.recruitSoldier( new Soldier( { name: "Cesar", id: "0003", company: "A" } ) ); marshal.recruitSoldier( new Soldier( { name: "Javier", id: "0004", company: "B" } ) ); var companyB: Array = marshal.getFilteredCollection( Marshal.COMPANY, "B" ); trace( companyB ); var companyA: Array = marshal.getFilteredCollection( Marshal.COMPANY, "A" ); trace( companyA ); var nameWithP: Array = marshal.getFilteredCollection( Marshal.CONTAINS_LETTER, "P" ); trace( nameWithP ); var nameWithe: Array = marshal.getFilteredCollection( Marshal.CONTAINS_LETTER, "e" ); trace( nameWithe ); } }

Pero øquÈ pasa si ahora el Profesor quiere un desfile con los soldados cuyo n˙mero de serie sea menor que 10.000?. HabrÌa que cambiar el cÛdigo del Mariscal. øY si ahora el Profesor quiere...? (ya sabes, al final siempre pasa lo mismo, los genios del mal no hacen m·s que pedir y pedir). El cÛdigo del Mariscal, tal y como est· no es muy escalable. No es facil aÒadir nuevas condiciones de filtrado. Pero Èse no es el ˙nico problema. Con cada iteraciÛn del bucle, hay que chequear todas las posibles condiciones, lo cual es, por decirlo suavemente, poco elegante. Y el Profesor Dispar puede estar loco, pero desde luego no es idiota, y quiere que su cÛdigo sea claro, compacto, f·cil de mantener y elegante.

AsÌ pues, el Profesor hace memoria, y recuerda (aquÌ es donde van los truenos y rel·mpagos) sus tiempos de estudiante, cuando aprendiÛ el patrÛn (no, lo siento, el nombre lo tienes que adivinar t˙).

Si encontrara una forma de darle al Mariscal un paquete que contuviera la condiciÛn a chequear, de forma que el Mariscal no necesitara conocer lo que est· chequeando, el problema estarÌa resuelto. El Mariscal delegarÌa el chequeo de esa condiciÛn en lo que le entregue el Profesor, y se podrÌan aÒadir nuevas condiciones sin cambiar el cÛdigo del Mariscal. Veamos cÛmo.

El Profesor le va a entregar, por tanto, al Mariscal una clase que ser· la responsable de realizar el filtro. Pero puede haber muchas condiciones, por lo tanto, todas las clases que se le puedan dar al Mariscal deber·n implementar un interfaz com˙n para que el Mariscal las pueda manejar de forma anÛnima.

Ese interfaz puede ser:

This interface could be:

interface ICompare { public function matches( checkValues: Object ): Boolean; }

Por tanto, uno de los filtros podrÌa escribirse asÌ:

class NameContainsLetterFilter implements ICompare { private var refValue: String; function NameContainsLetterFilter( ref: String ) { this.refValue = ref; } public function matches( checkValues: Object ): Boolean { return ( checkValues.name.indexOf( this.refValue ) != -1 ); } }

Y el cÛdigo del Mariscal pasarÌa a ser:

class Marshal { private var soldiersList: Array; function Marshal( ) { this.soldiersList = new Array( ); } public function recruitSoldier( newSoldier: Soldier ) { this.soldiersList.push( newSoldier ); } public function getFilteredCollection( filter: ICompare ): Array { var returnVal: Array = new Array( ); var numSoldiers: Number = this.soldiersList.length; var actualSoldierInfo: Object; for( var k: Number = 0; k< numSoldiers; k++ ) { actualSoldierInfo = this.soldiersList[ k ].getPublicInfo( ); if( filter.matches( actualSoldierInfo ) ) { returnVal.push( this.soldiersList[ k ] ); } } return returnVal; } }

Y el Profesor Dispar:

class Professor { function Professor( ) { } public static function initApp( ) { var professor : Professor = new Professor( ); var marshal : Marshal = new Marshal( ); marshal.recruitSoldier( new Soldier( { name: "Peter", id: "0001", company: "B" } ) ); marshal.recruitSoldier( new Soldier( { name: "Paul", id: "0002", company: "B" } ) ); marshal.recruitSoldier( new Soldier( { name: "Cesar", id: "0003", company: "A" } ) ); marshal.recruitSoldier( new Soldier( { name: "Javier", id: "0004", company: "B" } ) ); var nameWithP: Array = marshal.getFilteredCollection( new NameContainsLetterFilter( "P" ) ); trace( nameWithP ); var nameWithe: Array = marshal.getFilteredCollection( new NameContainsLetterFilter( "e" ) ); trace( nameWithe ); } }

AÒadir nuevos filtros ser· sencillo. Por ejemplo, para aÒadir un filtro utilizando el nombre de la compaÒÌa, el Profesor Dispar lo podrÌa encapsular en una clase:

class CompanyFilter implements ICompare { var refValue: String; public function CompanyFilter( ref: String ) { this.refValue = ref; } public function matches( checkValues: Object ): Boolean { return ( checkValues.company == this.refValue ); } }

Otro filtro m·s:

class IDLowerFilter implements ICompare { private var refValue: String function IDLowerFilter( ref: String ) { this.refValue = ref; } public function matches( checkValues: Object ): Boolean { var ref : Number = parseInt( this.refValue, 10 ); var check : Number = parseInt( checkValues.id, 10 ); return ( check<= ref ); } }

Y para utilizar esos filtros:

class Professor { function Professor( ) { } public static function initApp( ) { var professor : Professor = new Professor( ); var marshal : Marshal = new Marshal( ); marshal.recruitSoldier( new Soldier( { name: "Peter", id: "0001", company: "B" } ) ); marshal.recruitSoldier( new Soldier( { name: "Paul", id: "0002", company: "B" } ) ); marshal.recruitSoldier( new Soldier( { name: "Cesar", id: "0003", company: "A" } ) ); marshal.recruitSoldier( new Soldier( { name: "Javier", id: "0004", company: "B" } ) ); var nameWithP: Array = marshal.getFilteredCollection( new NameContainsLetterFilter( "P" ) ); trace( nameWithP ); var nameWithe: Array = marshal.getFilteredCollection( new NameContainsLetterFilter( "e" ) ); trace( nameWithe ); var companyB: Array = marshal.getFilteredCollection( new CompanyFilter( "B" ) ); trace( companyB ); var companyA: Array = marshal.getFilteredCollection( new CompanyFilter( "A" ) ); trace( companyA ); var idLessThan2: Array = marshal.getFilteredCollection( new IDLowerFilter( "0002" ) ); trace( idLessThan2 ); var idLessThan3: Array = marshal.getFilteredCollection( new IDLowerFilter( "0003" ) ); trace( idLessThan3 ); } }

De esta forma, el Profesor ha sido capaz de cambiar los filtros sin cambiar el cÛdigo del Mariscal.

Pero hay que hacer algunas consideraciones. Todos los filtros implementan un interfaz. Se podrÌa discutir sobre si deberÌan extender de una clase base o no, pero el hacerlos extender de una misma clase base les harÌa perder flexibilidad. Por ejemplo, imagina que hay que realizar un filtro basado en c·lculos numÈricos... Por eso, aunque en este ejemplo podrÌa ser v·lido el usar una clase base, el polimorfismo se basar· en la implementaciÛn de un interfaz com˙n.

Resumiendo, øquÈ ha conseguido el Profesor?. En primer lugar, ha seguido el Open-closed principle. El Mariscal est· abierto a extensiones, pero cerrado a modificaciones. Dicho de otra forma, se puede extender su funcionalidad sin cambiar su cÛdigo.

En segundo lugar, se ha delegado la construcciÛn de los filtros en una entidad totalmente distinta a la que los ejecuta, es decir, se ha desacoplado totalmente la creaciÛn de esos filtros.

El profesor rÌe histÈricamente mientras la imagen se funde a negro... Pero rÌe no sÛlo por la resoluciÛn de su aventura de hoy, sino porque sabe que este patrÛn se puede mejorar, por ejemplo, utilizando filtros que sean la composiciÛn de otros filtros... (continuar·...)

Ahora te toca a tÌ. øCÛmo se llama el patrÛn? (pista: el Profesor ha desacoplado un algoritmo de su receptor, encapsul·ndolo en una clase separada)

Descarga el cÛdigo fuente. (si te apetece, claro)

Octubre 16, 2005

[Cocoa] Unos links

En las últimas semanas han aparecido varios posts interesantes sobre Cocoa. En Theobroma Cacao, Scott Stevenson ha publicado dos artículos sobre el key-value coding:

En Mac Geekery, codepoet ha publicado dos tutoriales sobre Core Data:

Octubre 09, 2005

[Cocoa] Manejo de memoria para programadores Java

Informit ha publicado hace unos días un artículo titulado A Java Programmer's Introduction to Objective-C: Memory Management.

El artículo compara cómo manejan la memoria ambos lenguajes, y hace una introducción al manejo de memoria para los que estamos acostumbrados a trabajar con recolector de basura.

A Java Programmer's Introduction to Objective-C: Memory Management