2009-05-15 9 views
10

J'ai une situation intéressante et je me demande s'il y a une meilleure façon de le faire. La situation est la suivante, j'ai une structure arborescente (un arbre de syntaxe abstraite, en particulier) et certains nœuds peuvent contenir des nœuds enfants de différents types, mais tous étendus à partir d'une classe de base donnée.Sécurité de type, génériques Java et interrogation

Je veux fréquemment faire des requêtes sur cet arbre et j'aimerais récupérer les sous-types spécifiques qui m'intéressent. J'ai donc créé une classe de prédicat que je peux ensuite passer dans une méthode de requête générique. Au début, j'avais une méthode de requête qui ressemblait à ceci:

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

où l'argument Class a été utilisé juste pour indiquer le type de retour. Ce qui m'a dérangé à propos de cette approche, c'est que tous mes prédicats étaient déjà pour des types spécifiques, donc il y a des informations redondantes ici. Un appel typique pourrait ressembler à:

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

Je remaniée avec comme ceci:

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

Si l'interface IElementPredicate ressemble à ceci:

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

Le point ici est que le prédicat L'interface est développée pour fournir l'objet Class à la place. Il rend l'écriture de la méthode findAll un peu plus de travail et il ajoute un peu plus de travail dans l'écriture du prédicat, mais ce sont deux choses minuscules "une seule fois" et cela rend l'appel de requête beaucoup plus agréable parce que vous ne doit ajouter l'argument supplémentaire (potentiellement redondant), par exemple

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

Je n'ai pas remarqué ce modèle auparavant. Est-ce une façon typique de traiter la sémantique des génériques Java? Juste curieux de savoir s'il me manque un meilleur motif.

Commments?

MISE À JOUR:

Une question est ce que vous avez besoin de la classe pour? Voici la mise en œuvre 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; 
} 

Il est vrai ce match ne prend que T, je dois vous assurer que l'objet est un T avant que je puisse l'appeler. Pour cela, j'ai besoin des méthodes "isInstance" et "cast" de Class (pour autant que je sache).

Répondre

1

Le "modèle" le plus proche que je pense est le jeton de type et Generics Tutorial le recommande. Vous pouvez également transformer le prédicat de base en super-type-token (gadget Gafter a.k.a.) et enregistrer le couple supplémentaire de lignes lors de la définition de nouveaux prédicats.

1

Si vous le souhaitez, vous pouvez utiliser le modèle de visiteur, ou des variantes, pour contourner la construction et le casting explicites. Voir the hibernate wiki page on this. Je pensais à si vous allez contourner votre problème complètement compte tenu des particularités de l'effacement de type, et je ne suis pas entièrement sûr que cela va fonctionner tout le chemin.

edit: J'ai vu votre ajout. Si vous souhaitez que votre hiérarchie de prédicats suive une hiérarchie de visiteurs, vous n'avez pas besoin de la distribution avant l'appel de correspondance. Comme si (non testé):

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

J'utilise fréquemment les visiteurs (dans ce même projet, en fait) mais dans ce cas particulier, le modèle de visiteur ajouterait beaucoup plus de code et de complexité que je ne le pense. –

+0

Je suis d'accord avec vous, c'est un peu comme un changement de complicateur dans la façon dont il a de grands effets secondaires. Il est difficile d'inverser le généralisme comme vous voulez. Je crois que votre solution est la meilleure, parce que sans un visiteur je crois que vous auriez besoin d'une liaison tardive, ou une vérification de type explicite (de la même manière que equal() fonctionne). Votre solution est meilleure car elle centralise le contrôle de type au lieu de mettre ce fardeau sur les implémenteurs. Vous voyez également ce problème dans les implémentations génériques Comparable. –

+0

Et avec une liaison tardive, je voulais dire que Java devrait implémenter une résolution de méthode surchargée basée sur le type d'objet, pas sur le type de référence. C'est essentiellement ce que vous avez implémenté dans votre code. –

1

Votre structure arborescente ressemble beaucoup à un objet DOM XML. Avez-vous envisagé de traduire votre arbre dans une structure DOM et d'utiliser XPath pour faire vos requêtes? Ce code peut être beaucoup moins personnalisé.

0

Je pense que c'est une façon agréable et propre de le faire et je le ferais probablement de la même manière.

Questions connexes