2009-05-15 13 views
10

Ho una situazione interessante e mi chiedo se c'è un modo migliore per farlo. La situazione è questa, ho una struttura ad albero (un albero sintattico astratto, in particolare) e alcuni nodi possono contenere nodi figlio di vario tipo ma tutti estesi da una data classe base.Tipo sicurezza, generics Java e query

Desidero eseguire frequentemente query su questo albero e vorrei recuperare i sottotipi specifici a cui sono interessato. Ho quindi creato una classe di predicati che è quindi possibile passare in un metodo di query generico. In un primo momento ho avuto un metodo di query che si presentava così:

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

dove l'argomento Class è stato utilizzato solo per indicare il tipo di ritorno. Ciò che mi ha infastidito su questo approccio è che tutti i miei predicati erano già per tipi specifici, quindi qui sono disponibili informazioni ridondanti. Una chiamata tipica potrebbe essere simile:

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

Così ho riscritta così:

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

Dove l'interfaccia IElementPredicate si presenta così:

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

Il punto qui è che il predicato l'interfaccia viene espansa per fornire invece l'oggetto Class. Rende la scrittura del metodo effettivo findAll un po 'più di lavoro e aggiunge un po' più di lavoro nella scrittura del predicato, ma sono entrambe cose "di una volta" essenzialmente minuscole e rende la query chiamata molto più bella perché non aggiungere l'argomento extra (potenzialmente ridondante), ad es

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

Non ho notato questo modello prima. È questo un modo tipico di affrontare la semantica dei generici di Java? Solo curioso di sapere se mi manca qualche schema migliore.

Commmenti?

UPDATE:

Una domanda era che cosa avete bisogno della Classe per? Ecco l'implementazione di 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; 
} 

Se è vero che incontro vuole solo un T, ho bisogno di assicurarsi che l'oggetto è una T prima che io possa chiamare. Per questo, ho bisogno dei metodi "isInstance" e "cast" di Class (per quanto posso dire).

risposta

1

Il "modello" più vicino, penso sia token e Generics Tutorial li raccomandi. È anche possibile convertire il predicato di base in super-type-token (a.k.a. gadget Gafter) e salvare le ulteriori due linee quando si definiscono nuovi predicati.

1

Se lo si desidera, è possibile utilizzare il modello o le varianti del visitatore per aggirare la costruzione e il casting espliciti. Vedi the hibernate wiki page on this. Stavo pensando se riuscirai a risolvere il tuo problema considerando completamente le peculiarità della cancellazione del testo, e non sono del tutto sicuro che funzionerà fino in fondo.

modifica: ho visto la tua aggiunta. Se si desidera che la gerarchia dei predicati segua una gerarchia di visitatori, non sarà necessario il cast prima della chiamata di incontro. Ad esempio (non testato):

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 spesso i visitatori (in questo stesso progetto, di fatto) ma in questo caso particolare, il modello di visitatore aggiungerebbe molto più codice e complessità di quanto ritenga necessario. –

+0

Sono d'accordo con te, è una specie di complicatore che cambia nel modo in cui ha grandi effetti collaterali. È difficile invertire il generalismo come vuoi tu. Credo che la tua soluzione sia la migliore, perché senza un visitatore credo che avresti bisogno di un binding tardivo o di un controllo esplicito del tipo (nel modo in cui equals() funziona). La tua soluzione è migliore perché centralizza il controllo dei tipi invece di attribuire quel peso agli implementatori. Vedi questo problema anche nelle generiche implementazioni Comparabili. –

+0

E con l'associazione tardiva intendevo che Java doveva implementare una risoluzione del metodo sovraccaricata basata sul tipo di oggetto, non sul tipo di riferimento. Questo è fondamentalmente ciò che hai implementato nel tuo codice. –

1

La struttura ad albero suona molto simile a un oggetto DOM XML. Hai pensato di tradurre il tuo albero in una struttura DOM e usare XPath per fare le tue domande? Potrebbe essere molto meno codice personalizzato.

0

Penso che sia un modo carino e pulito di farlo e probabilmente lo farei allo stesso modo.