Creación: 13-01-2010
Índice de contenidos
1.Introducción
2. Entorno
3.El ejemplo sencillo
4.El ejemplo no tan sencillo
5.Conclusiones
6.Sobre el autor
1. Introducción
Básicamente el patrón Visitor es el que nos permite separar el recorrido de un grafo, de la operación que se hará con cada uno de los nodos de ese grafo.
Este patrón de diseño nos permite añadir nuevos tratamientos sobre los nodos del grafo, o añadir nuevos recorridos, de forma independiente y sin que uno afecte al otro.
En este tutorial vamos a ver como podemos usar la librería de Apache commons-collections (http://commons.apache.org/collections) para implementar de forma sencilla un Visitor que se recorra todos los elementos de una colección.
2. Entorno
El tutorial está escrito usando el siguiente entorno:
- Hardware: Portátil MacBook Pro 17′ (2.93 GHz Intel Core 2 Duo, 4GB DDR3 SDRAM, 128GB Solid State Drive).
- NVIDIA GeForce 9400M + 9600M GT with 512MB
- Sistema Operativo: Mac OS X Snow Leopard 10.6.1
- JDK 1.6.0_17
- Maven 2.2.1
- commons-collections 3.2.1
3. El ejemplo sencillo
Dentro de la librería commons-collections nos encontramos con la clase CollectionUtils
, y dentro de esta podemos ver el método estático forAllDo()
. Este método tiene dos
parámetros de entrada:
- la colección que queremos recorrer. Como bien dice el nombre del método, se recorrerá todos los elementos de la colección.
- una
Closure
. Esta clase se llamará con cada elemento de la colección, y es aquí donde haremos el tratamiento que nos interese.
Un ejemplo sencillo sería mostrar por consola cada uno de los elementos de la colección:
CollectionUtils.forAllDo(miColeccion, new Closure() { public void execute(Object input) { System.out.println(input.toString()); } }
Evidentemente para hacer esto podríamos haber hecho un bucle por la colección e imprimir los elementos. Pero en tal caso, el recorrido y el tratamiento estarían ligados, y es precisamente lo que estamos intentado evitar en este tutorial.
4. El ejemplo no tan sencillo
Imaginemos ahora que tenemos dos clases que no tienen ningún tipo de jerarquía entre ellas. Ambas clases tienen un valor numérico, y queremos obtener la media de este valor para los objetos de cada uno de estos dos tipos:
public class Foo { public double getFooValue() {...} }
public class Bar { public double getBarValue {…} }
Vamos a hacer una clase que sea capaz de, dada una colección de objetos de tipo Foo
o tipo Bar
, podamos calcular la media del valor de todos los objetos de la colección.
public class AverageCalculator<T> { private double sum; private class AverageTransformer implements Transformer { public Object transform(Object input) { sum += ((Double)input).doubleValue(); return null; } }; public double calculate(Collection<T> collection, Transformer valueAccessor) { if (collection.isEmpty()) return 0; sum = 0; CollectionUtils.forAllDo(collection, ClosureUtils.asClosure( TransformerUtils.chainedTransformer(valueAccessor, new AverageTransformer()))); return sum / collection.size(); } }
Vemos que estamos usando la interfaz Closure
y la clase Transformer
. La diferencia básica entre estas dos interfaces es que el método execute()
de Closure
no devuelve ningún valor, mientras que el método transform()
de Transformer
devuelve un Object
.
La gracia está en cómo hemos unido las dos transformaciones con el método chainedTransformer()
. De esta manera el resultado del Transformer
valueAccessor se pasará como parámetro de entrada (input) al siguiente Transformer
. Es decir, el primer Transformer
obtendrá el valor del elemento de la colección y este valor se pasará al
AverageTransformer
que se encargará de ir acumulándolo.
Para usar esta clase bastará con hacer:
final double averageFooValues = new AverageCalculator<Foo>().calculate(coleccionObjetosFoo, new Transformer() { public Object transform(Object input) { return Double.valueOf(((Foo)input).getFooValue()); } });
final double averageBarValues = new AverageCalculator<Bar>().calculate(coleccionObjetosBar, new Transformer() { public Object transform(Object input) { return Double.valueOf(((Boo)input).getBooValue()); } });
De hecho lo que acabamos de construir es una clase capaz de sacar la media de los elementos de una colección, independientemente de su tipo. Sólo necesitamos saber como extraer el valor de cada elemento y crear el Transformer adecuado.
5. Conclusiones
El ejemplo quizás parece un poco complejo, pero realmente el diseño es muy potente (intenta seguir los principio de SOLID,
http://www.lostechies.com/blogs/chad_myers/archive/2008/03/07/pablo-s-topic-of-the-month-march-solid-principles.aspx).
De forma que podemos crear fácilmente nuevas clases para, en vez de calcular la media, calcular cualquier otra cosa sobre los elementos. O podemos fácilmente crear un nuevo recorrido (por ejemplo sólo tratar los elementos pares) y usar este nuevo recorrido en vez del método forAllDo()
.
Es fundamental conocer las librerías que están a nuestro alcance y un poquito de patrones. Con esto podemos hacer diseños muy interesantes escribiendo muy poco código.
6. Sobre el autor
Alejandro Pérez García, Ingeniero en Informática (especialidad de Ingeniería del Software) y Certified ScrumMaster
Socio fundador de Autentia (Formación, Consultoría, Desarrollo de sistemas transaccionales)
mailto:alejandropg@autentia.com
Autentia Real Business Solutions S.L. – «Soporte a Desarrollo»