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
, Таким образом, по крайней мере, ручная отправка учитывается между посетителями.