Patrón Visitor con commons-collections y sus Closures

0
8917

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»

http://www.autentia.com

 

Alejandro es socio fundador de Autentia y nuestro experto en Java EE, Linux y optimización de aplicaciones empresariales. Ingeniero en Informática y Certified ScrumMaster. Seguir @alejandropgarci Si te gusta lo que ves, puedes contratarle para darte ayuda con soporte experto, impartir cursos presenciales en tu empresa o para que realicemos tus proyectos como factoría (Madrid). Puedes encontrarme en Autentia: Ofrecemos servicios de soporte a desarrollo, factoría y formación.

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad