Thursday, November 09, 2006

Reusando codigo, siguiente nivel

El codigo para este articlo esta escrito en C# (probado en VS2005)

Hace algunos dias (mientras andaba de vacaciones) lei un articulo en latindevelopers: Ordenando objetos con IComparer; el cual me recordo un error muy comun que cometen muchos programadores: usar sentencias condicionales dentro de (no muy obvios) ciclos (por ejemplo en callbacks o eventos paint) lo cual resulta en un tipo de (no muy obvio) codigo duplicado.

En Delphi encontramos este error muy comunmente en el desarrollo de componentes, especificamente en el evento Paint donde usan sentencias "if" para decidir que font usar, colores, etc. y lo que es peor, muchas veces crean una instancia (de las clases que se necesitan) en cada iteracion; por supuesto, todos esos objetos no cambian, a menos que cambies una propiedad del componente!!, por lo cual esos objetos deberian ser "cacheados" (cached) y mantenerlos en variables donde simplemente se puedan reusar; asi es, reusar codigo no significa solamente re-usar clases, o partir el codigo en metodos mas pequeños para hacerlos reusables, cachear objetos puede ser una forma de reusar codigo ya que nos evita sentencias condicionales y las reemplaza con llamadas directas al codigo que requerimos.

Vamos a ver el codigo del ejemplo original, especificamente la clase PersonComparer, esta tiene un constructor que toma un parametro SortField y lo guarda en un campo que se usa despues en el metodo Compare

public PersonComparer(SortField sortField) {
this.sortField = sortField;
}
public int Compare(object x, object y) {
if (!((x is Person) & (y is Person))) {
throw new
ArgumentException("Los objetos deben ser de tipo Person.");
}

switch (sortField) {
case SortField.Name:
return
Comparer.DefaultInvariant.Compare(
((Person)x).Name, ((Person)y).Name);

case SortField.LastName:
return
Comparer.DefaultInvariant.Compare(
((Person)x).LastName, ((Person)y).LastName);

case SortField.Age:
return
((Person)x).Age - ((Person)y).Age;

default:
throw new
ArgumentException("Tipo de ordenamiento desconocido.");
}
}

Talvez no es tan facil verlo aqui porque el ejemplo no usa genericos, vamos a ver que podemos hacer usando genericos (si hay un tipo TimeZone2 en el .NET framework, porque no puedo yo escribir una clase PersonCompare2? =oP)

    public class PersonComparer2<T> : System.Collections.Generic.IComparer<T> where T:Person {
delegate int ComparerDelegate(T x, T y);
ComparerDelegate comparerDelegate;

Ahora tenemos la declaracion de un delegado, una variable de ese tipo, y eliminamos la variable del tipo SortField, vamos a ver el constructor:

public PersonComparer2(SortField sortField) {
switch (sortField) {
case SortField.Name:
comparerDelegate = delegate(T x, T y) {
return x.Name.CompareTo(y.Name);
};
break;
case SortField.LastName:
comparerDelegate = delegate(T x, T y) {
return x.LastName.CompareTo(y.LastName);
};
break;
case SortField.Age:
comparerDelegate = delegate(T x, T y) {
return x.Age - y.Age;
};
break;
default:
throw new
ArgumentException("Tipo de ordenamiento desconocido");
}
}

En vez de cachear la variable SortField, aqui estoy cacheando el metodo que se ejecutara cuando la comparacion se lleve a cabo; finalmente, vamos a ver el metodo Compare:

public int Compare(T x, T y) {
return comparerDelegate(x, y);
}

Puedes ver el beneficio? (si quieres ve una pagina arriba para que veas el codigo original) la decision de que metodo usar para la comparacion se ejecuta solo una vez (en el constructor), y ya despues podemos usarlo directamente, en vez de hacer la misma pregunta una y otra vez en cada iteracion.


Cada vez que veas una sentencia condicional dentro de un ciclo o un metodo que se ejecute en algun tipo de loop (como eventos Paint, callback, etc) considera usar esta tecnica, te ayudara a construir codigo mucho mas estructurado y mas rapido, tambien considera usar arreglos o diccionarios de metodos para reemplazar tus sentencias condicionales.


El concepto aplica a programacion en general, pero la implementacion puede variar (bastante) de un lenguage a otro, la idea principal es cachear objetos / metodos de alguna manera que no tengas que repetir sentencias condicionales para crear objetos o decidir que bloque de codigo ejecutar.


El codigo completo (junto con el codigo original) lo puedes encontrar aqui, como siempre, juega con el, aprende de el, y mejoralo

No comments: