2009-05-15 17 views
10

Tengo una situación interesante y me pregunto si hay una mejor manera de hacerlo. La situación es la siguiente: tengo una estructura de árbol (un árbol de sintaxis abstracta, específicamente) y algunos nodos pueden contener nodos secundarios de varios tipos pero todos extendidos desde una clase base determinada.Seguridad tipo, genéricos Java y consultas

Quiero hacer consultas con frecuencia en este árbol y me gustaría obtener los subtipos específicos que me interesan. Así que creé una clase de predicado que luego puedo pasar a un método de consulta genérico. Al principio no tenía un método de consulta que se veía así:

public <T extends Element> List<T> findAll(IElementPredicate pred, Class<T> c); 

donde se utilizó el argumento Class simplemente para indicar el tipo de retorno. Lo que me molestó acerca de este enfoque es que todos mis predicados ya eran para tipos específicos, así que aquí hay información redundante. Una llamada típica podría ser:

List<Declaration> decls = 
    scope.findAll(new DeclarationPredicate(), Declaration.class); 

Así que refactorizado así:

public <T extends Element> List<T> findAll(IElementPredicate<T> pred); 

Cuando la interfaz IElementPredicate se ve así:

public interface IElementPredicate<T extends Element> { 
    public boolean match(T e); 
    public String getDescription(); 
    public Class<T> getGenericClass(); 
} 

El punto aquí es que el predicado la interfaz se expande para proporcionar el objeto Class en su lugar. Hace que escribir el método real findAll sea un poco más laborioso y agrega un poco más de trabajo al escribir el predicado, pero esas son cosas "pequeñas" y hacen que la llamada sea mucho más agradable porque no lo hace tiene que agregar el argumento extra (potencialmente redundante), por ej.

List<Declaration> decls = scope.findAll(new DeclarationPredicate()); 

No he notado este patrón antes. ¿Es esta una forma típica de tratar con la semántica de los genéricos de Java? Solo curiosidad si me falta un patrón mejor.

Comentarios?

ACTUALIZACIÓN:

Una pregunta era ¿qué es lo que necesita la clase de? Aquí está la implementación de findAll:

public <T extends Element> List<T> findAll(IElementPredicate<T> pred) { 
    List<T> ret = new LinkedList<T>(); 
    Class<T> c = pred.getGenericClass(); 
    for(Element e: elements) { 
     if (!c.isInstance(e)) continue; 
     T obj = c.cast(e); 
     if (pred.match(obj)) { 
      ret.add(c.cast(e)); 
     } 
    } 
    return ret; 
} 

Si bien es cierto que sólo se necesita un partido de T, que necesito para asegurarse de que el objeto es un T antes de que pueda llamarlo. Para eso, necesito los métodos "isInstance" y "cast" de Class (hasta donde puedo decir).

Respuesta

1

El "patrón" más cercano creo que es tipo token y Generics Tutorial los recomienda. También puede convertir el predicado base en super-type-token (un gadget de Gafter) y guardar el par adicional de líneas al definir nuevos predicados.

1

Si lo desea, puede utilizar el patrón de visitante o las variantes para evitar la construcción y el fundido explícitos. Ver the hibernate wiki page on this. He estado pensando si va a solucionar su problema por completo, teniendo en cuenta las peculiaridades del borrado de tipos, y no estoy del todo seguro de que funcione todo el camino.

editar: Vi tu adición. Si quiere que su jerarquía de predicados siga una jerarquía de visitantes, no necesitaría el elenco antes de la llamada de coincidencia. Como tal (no probado):

interface Element { 
    public boolean accept(ElementPredicateVisitor v); 
} 

class Declaration implements Element { 
    public boolean accept(ElementPredicateVisitor v) { 
     return v.visit(this); 
    }  
} 

class TaxReturn implements Element { 
    public boolean accept(ElementPredicateVisitor v) { 
     return v.visit(this); 
    }  
} 


interface IElementPredicate { 
    public void match(Element e); 
} 

class ElementPredicateVisitor implements IElementPredicate { 
    public boolean match(Element e) { 
     return e.accept(this); 
    } 
    /** 
    * default values 
    */ 
    boolean visit(Declaration d) { return false; } 
    boolean visit(TaxReturn tr) { return false; } 
} 

class DeclarationNamePredicate extends ElementPredicateVisitor { 
    boolean visit(Declaration d) { 
     return d.dSpecificExtraName() == "something" 
    } 
} 

class TaxReturnSumPredicate extends ElementPredicateVisitor { 
    boolean visit(TaxReturn tr) { 
     return tr.sum() > 1000; 
    } 
} 


public <T extends Element> List<T> findAll(IElementPredicate pred) { 
    List<T> ret = new LinkedList<T>(); 
    for(Element e: elements) {    
     if (pred.match(obj)) { 
       ret.add((T) obj); 
     } 
    } 
    return ret; 
} 
+0

Uso los visitantes con frecuencia (en este mismo proyecto, de hecho) pero en este caso particular, el patrón de visitante agregaría mucho más código y complejidad de lo que creo que sería necesario. –

+0

Estoy de acuerdo contigo, es una especie de interruptor de complicación en la forma en que tiene grandes efectos secundarios. Es difícil revertir el generalismo como lo desea. Creo que su solución es la mejor, porque sin un visitante creo que necesitaría un enlace tardío o una verificación de tipo explícita (en la forma en que equals() funciona). Su solución es mejor porque centraliza la verificación de tipos en lugar de poner esa carga en los implementadores. También ves este problema en las implementaciones genéricas Comparables. –

+0

Y con el enlace tardío quise decir que Java debería implementar una resolución de método sobrecargada basada en el tipo de objeto, no en el tipo de referencia. Esto es básicamente lo que implementó en su código. –

1

La estructura de árbol se parece mucho a un objeto XML DOM. ¿Has considerado traducir tu árbol en una estructura DOM y usar XPath para hacer tus consultas? Puede ser mucho menos código personalizado.

0

Creo que es una forma agradable y limpia de hacerlo y probablemente lo haga de la misma manera.