Thursday, August 31, 2006

Arreglos de metodos en C#

Cuantas veces has escrito codigo mas o menos asi?:

if (someValue == SomeEnum.Type1)
Method1("value 1");
else if (someValue == SomeEnum.Type2)
Method2("value 2");
else if (someValue == SomeEnum.Type2)
Method3("value 3");

O talvez usaste un "switch" para lograr esto mismo.
Inmediatamente podemos ver un patron ahi: Metodo1, Metodo2 y Metodo3 todos tienen la misma estructura y se ejecutan cuando pasamos los valores Type1, Type2 y Type3. No seria bueno si pudieramos reducir ese codigo a una sola linea?


Eso es exactamente lo que los arreglos de metodos pueden hacer por ti, vamos a ver un ejemplo, antes de poderlos usar tenemos que prepararlos.

Como todos los metodos comparten la misma estructura, esto quiere decir que podemos usar un delegado para representar todos los metodos

delegate void AddStringDelegate(string someValue);

Ahora declaramos un arreglo de delegados:

AddStringDelegate[] addStringMethods;

tambien vamos a necesitar un tipo enum para accesar los metodos del arreglo:
enum StringType {
Type1,
Type2,
Type3,
Type4
}

Luego tenemos los metodos en si:

void AddStringType1(string someValue) {
OutputText(string.Format("String Type 1: {0}", someValue));
}
void AddStringType2(string someValue) {
OutputText(string.Format("String Type 2: {0}", someValue));
}
void AddStringType3(string someValue) {
OutputText(string.Format("String Type 3: {0}", someValue));
}
Finalmente, en el constructor de nuestra clase, podemos asignar los metodos al arreglo usando el delegado y de esta manera obtenemos nuestro arreglo de metodos:
addStringMethods = new AddStringDelegate[3];
addStringMethods[(int)StringType.Type1] = new AddStringDelegate(AddStringType1);
addStringMethods[(int)StringType.Type2] = new AddStringDelegate(AddStringType2);
addStringMethods[(int)StringType.Type3] = new AddStringDelegate(AddStringType3);

Ya esta listo para ser usado, vamos a ver como se veria el codigo que presente al inicio, ahora usando nuestro arreglo:

public void AddString(string someValue, StringType stringType) {
addStringMethods[(int)stringType](someValue);
}

Como puedes ver, podemos accesar el metodo requerido pasando el tipo (y convirtiendolo a entero porque C# no nos deja hacerlo si no lo convertimos); nuestro codigo ahora se redujo a una simple linea, pasamos el enum stringType y este ejecuta el metodo correcto automaticamente.


El uso de esta tecnica por ahora se las dejo de tarea a ustedes, yo no recomendaria usar esta tecnica en una clase pequeña que creamos y tiramos constantemente, pero en algunos casos talvez aun asi podria ser util.


Yo creo que seria especialmente util si tuvieramos este tipo de patron (donde queremos ejecutar ciertos metodos dependiendo de ciertos values de enumeracion) dentro de un patron singleton, o donde la clase potencialmente podria vivir mucho mas, podrias hacer todo el trabajo de inicializar tu arreglo de metodos y asi poder reutilizarlos efectivamente.


Aqui esta todo el codigo para este ejemplo, en mi siguiente post les mostrare como podemos usar esta tecnica para implementar un tipo de factory pattern (patron de fabrica) o mas concretamente el simple factory pattern (patron de fabrica simple).


Precisamente hoy mismo acabo de usar este patron (que yo le llamo "simplified simple factory pattern") en mi proyecto actual, donde creo una instancia de la clase generadora y la reuso cada que necesito un objeto nuevo (dependiendo del tipo requerido)

class FileGeneratorBase {
delegate void AddStringDelegate(string someValue);

AddStringDelegate[] addStringMethods;

public FileGeneratorBase() {
addStringMethods = new AddStringDelegate[4];
addStringMethods[(int)StringType.Type1] = new AddStringDelegate(AddStringType1);
addStringMethods[(int)StringType.Type2] = new AddStringDelegate(AddStringType2);
addStringMethods[(int)StringType.Type3] = new AddStringDelegate(AddStringType3);
addStringMethods[(int)StringType.Type4] = new AddStringDelegate(AddStringType4);
}

public void AddString(string someValue, StringType stringType) {
addStringMethods[(int)stringType](someValue);
}
void AddStringType1(string someValue) {
OutputText(string.Format("String Type 1: {0}", someValue));
}
void AddStringType2(string someValue) {
OutputText(string.Format("String Type 2: {0}", someValue));
}
void AddStringType3(string someValue) {
OutputText(string.Format("String Type 3: {0}", someValue));
}
void AddStringType4(string someValue) {
OutputText(string.Format("String Type 4: {0}", someValue));
}
void OutputText(string someValue) {
Console.WriteLine(someValue);
}
}

class Program {
static void Main(string[] args) {
FileGeneratorBase fg = new FileGeneratorBase();
fg.AddString("some value", StringType.Type1);
fg.AddString("some other value", StringType.Type2);
fg.AddString("one last value", StringType.Type3);
fg.AddString("testing out of bounds", StringType.Type4);
Console.ReadLine();
}
}

Actualizado: Para incluir el tipo enum en la inicializacion de los metodos,
en lugar de 0..4 que tenia inicialmente, esto nos permitiria cambiar el orden
de los elementos de la enumeracion

4 comments:

Guillermo said...

Para el caso que has puesto, yo creo que es más corto de la otra manera.

De todas formas esto se podía hacer hasta en C que yo sepa.

Un Saludo

BlackTigerX said...

exacto, como siempre les digo, no hay que abusar las tecnicas, hay que saber usar las herramientas cuando estas nos van a hacer el trabajo mas facil

esto que postee aqui es solo eso, un ejemplo para ilustrar la tecnica, mas que el uso

salu2

Luna said...

Bonito cambio en el blogg, no lo reconoci, lo abri pero no pense mucho en el fondo hasta q vi lo q decia, jiji se ve bien.

buen fin de semana :D

Anonymous said...

Hola. Me quedé pensando en el código de ejemplo:

if (someValue == SomeEnum.Type1) Method1("value 1");else if (someValue == SomeEnum.Type2) Method2("value 2");else if (someValue == SomeEnum.Type2) Method3("value 3");

Y creo que la mejor manera de resolver esto sería con el Patrón Strategy.

Crearía una interfase con un metodo llamado "Methd".
Luego tres clases: "estrategia1", "estrategia2", "estrategia3", que implementen el método de esta interfase devolviendo cada una "value 1", "value 2", "value 3" respectivamente
Por último, crearía una clase Contexto que tenga por campo la interfase, un método que sea EjecutarEstrategia que reciba por parámetro una de las 3 estrategias creadas anteriormente.

Claro que esto implica no usar el enumerador SomeEnum sino la clase estrategia misma.

Mi mail es dcamacho31@hotmail.com.

Saludos!