Тип безопасности, Java-генерики и запросы

У меня интересная ситуация, и мне интересно, есть ли лучший способ сделать это. Ситуация такова: у меня есть древовидная структура (абстрактное синтаксическое дерево, в частности), а некоторые узлы могут содержать дочерние узлы разных типов, но все они расширены из заданного базового класса.

Я хочу часто делать запросы по этому дереву, и я хотел бы получить обратно определенные подтипы, которые меня интересуют. Поэтому я создал класс предикатов, который затем могу передать в общий метод запросов. Сначала у меня был метод запроса, который выглядел так:

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

где аргумент Class использовался только для указания типа возврата. Меня беспокоило то, что все мои предикаты были уже для определенных типов, поэтому здесь имеется избыточная информация. Типичный вызов может выглядеть так:

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

Поэтому я реорганизовал его следующим образом:

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

Где интерфейс IElementPredicate выглядит следующим образом:

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

Дело в том, что интерфейс предиката расширяется, чтобы вместо этого предоставить объект Class. Это заставляет писать фактический findAll метод немного больше работы, и он добавляет немного больше работы по написанию предиката, но это оба по сути крошечные "одноразовые" вещи, и это делает запрос намного приятнее, потому что вы надеваете 't нужно добавить дополнительный (потенциально избыточный) аргумент, например

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

Я раньше этого не заметил. Является ли это типичным способом работы с семантикой Java-дженериков? Просто любопытно, если мне не хватает лучшего образца.

Commments?

UPDATE:

Вопрос в том, для чего нужен класс? Вот реализация 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;
}

Хотя верно, что соответствие принимает только T, мне нужно убедиться, что объект является T, прежде чем я могу его вызвать. Для этого мне нужны методы "isInstance" и "cast" класса (насколько я могу судить).

Ответы

Ответ 1

Самый близкий "шаблон", который я считаю, - это токен, и рекомендует учебное пособие по универсальным средствам. Вы также можете превратить базовый предикат в super-type-token (он же гаджет Gafter) и сохранить дополнительные пару строк при определении новых предикатов.

Ответ 2

Если вы хотите, вы можете использовать шаблон посетителя или варианты, чтобы обойти явное построение и литье. Смотрите страницу викинга hibernate в этом. Я думал о том, если вы полностью обойдете свою проблему, учитывая особенности стирания стилей, и я не совсем уверен, что это будет работать до конца.

edit: Я видел ваше дополнение. Если вы сделаете иерархию предикатов следующей иерархией посетителей, вам не понадобится бросок перед вызовом матча. Так же (непроверенный):

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;
}

Ответ 3

Ваша древовидная структура очень похожа на объект XML DOM. Рассматривали ли вы перевод своего дерева в структуру DOM и использование XPath для выполнения ваших запросов? Это может быть намного меньше пользовательского кода.

Ответ 4

Я думаю, что это хороший и чистый способ сделать это, и я, вероятно, сделаю это таким же образом.