Java - общий интерфейс для посетителей

Я использую шаблон посетителя для выполнения функций в иерархии классов. Например:

// Node.java

public abstract class Node {}

// Addition.java

public final class Addition extends Node {
    public final Node e1;
    public final Node e2;

    public Addition(final Node e1, final Node e2) {
        this.e1 = e1;
        this.e2 = e2;
    }
}

// Multiplication.java

public final class Multiplication extends Node {
    public final Node e1;
    public final Node e2;

    public Multiplication(final Node e1, final Node e2) {
        this.e1 = e1;
        this.e2 = e2;
    }
}

// Constant.java

public final class Constant extends Node {
    public final int value;

    public Constant(final int value) {
        this.value = value;
    }
}

Поскольку у меня разные функции, берущие узел и возвращающие разные типы, я определяю интерфейс для моих посетителей:

public interface INodeVisitor<T> {
    public abstract T visit(final Addition add);
    public abstract T visit(final Multiplication add);
    public abstract T visit(final Constant add);

    public default T visit(final Node node) {
        throw new RuntimeException("Unknown node: " + node.getClass() + ".");
    }
}

Тогда я могу определить общий метод accept() для всех моих узлов, что позволяет мне определять новых посетителей без необходимости изменять узлы:

// I add this in the definition of every node.
public <T> T accept(final INodeVisitor<T> visitor) {
    return visitor.visit(this);
}

Теперь я могу определить функции на узлах, например:

// This visitor is a function on Nodes and returns an Integer.
public final class NodeEvaluationVisitor implements INodeVisitor<Integer> {
    public final Integer visit(final Addition add) {
        return add.e1.accept(this) + add.e2.accept(this);
    }

    public final Integer visit(final Multiplication mul) {
        return mul.e1.accept(this) * mul.e2.accept(this);
    }

    public final Integer visit(final Constant c) {
        return c.value;
    }
}

Это прекрасно работает:

public class Main {
    public static Integer eval(final Node n) {
        return n.accept(new NodeEvaluationVisitor());
    }

    public static void main(final String[] args) {
        final Node n = new Addition(new Multiplication(new Constant(5), new Constant(8)), new Constant(2));
        System.out.println(eval(n));
    }
}

Выход:

42

Это работает, потому что мой интерфейс имеет доступ ко всей иерархии. Моя проблема возникает когда Node определяется в projectAи его подклассы определены в projectB который зависит от projectA,

поскольку Node также необходимо определить accept() метод, который принимает INodeVisitor в качестве аргумента я определяю свой интерфейс в projectA, Затем я могу определить своих конкретных посетителей в projectB,

Однако это означает, что мой интерфейс должен быть общим для подклассов Nodeчто не представляется возможным. С этим интерфейсом:

public interface INodeVisitor<T> {
    public default T visit(final Node node) {
        throw new RuntimeException("Unknown node: " + node.getClass() + ".");
    }
}

Я получил:

Exception in thread "main" java.lang.RuntimeException: Unknown node: class Addition.
at INodeVisitor.visit(INodeVisitor.java:5)
at Addition.accept(Addition.java:14)
at Main.eval(Main.java:9)
at Main.main(Main.java:14)

Так как динамическая диспетчеризация Java выбирает default метод. Тот же результат с:

public interface INodeVisitor<T> {
    public default <U extends Node> T visit(final U node) {
        throw new RuntimeException("Unknown node: " + node.getClass() + ".");
    }
}

Единственный способ, который я нашел, чтобы сделать эту работу, это сделать диспетчеризацию вручную:

public interface INodeVisitor<T> {
    public abstract T visit(final Node node);
}

И так в NodeEvaluationVisitor:

@Override
public final Integer visit(final Node node) {
    if (node instanceof Addition)
        return visit((Addition) node);
    if (node instanceof Multiplication)
        return visit((Multiplication) node);
    if (node instanceof Constant)
        return visit((Constant) node);
    throw new RuntimeException("Unknown node: " + node.getClass() + ".");
}

Вопрос: Есть ли лучшее решение в Java, чем описанное выше неудовлетворительное решение для ручной отправки? Спасибо за чтение и за ваши предложения.

Изменить: Решение, которое я реализовал в конце (все еще неудовлетворительно), заключается в добавлении определенного интерфейса, скажем INodeSpecificVisitor<T>, в projectB, пусть посетители реализуют этот конкретный интерфейс и отправляют вручную в INodeSpecificVisitor, Таким образом, по крайней мере, ручная отправка учитывается между посетителями.

0 ответов

Другие вопросы по тегам