咨询:访问者模式同时处理两个可访问对象的简洁实现方案
处理二元操作的访问者替代方案
好问题!你碰到的其实是访问者模式在处理二元操作时的天然局限——标准访问者本来就是为单元素访问设计的,直接套用到需要同时处理两个对象的场景,就会出现你说的那种要写一堆重载、逻辑分散的麻烦情况。下面给你几个更简洁的解决方案,按需选择:
1. 改造访问者为「二元访问者」模式
我们可以扩展访问者的思路,设计一个专门处理双元素的二元访问者接口,让元素类配合完成双重分派。这种方式比你原来的方案更集中,避免逻辑散落在各个元素类里。
举个Java的例子:
核心接口与类
// 元素基础接口 interface Element { <T> T acceptBinary(BinaryVisitor<T> visitor, Element other); } // 数字元素实现 class NumberElement implements Element { private int value; public NumberElement(int value) { this.value = value; } public int getValue() { return value; } @Override public <T> T acceptBinary(BinaryVisitor<T> visitor, Element other) { // 将自己作为左元素,让另一个元素处理右分派 return other.acceptBinaryRight(visitor, this); } // 处理自己作为右元素的情况 public <T> T acceptBinaryRight(BinaryVisitor<T> visitor, NumberElement left) { return visitor.visit(left, this); } public <T> T acceptBinaryRight(BinaryVisitor<T> visitor, StringElement left) { return visitor.visit(left, this); } } // 字符串元素实现 class StringElement implements Element { private String value; public StringElement(String value) { this.value = value; } public String getValue() { return value; } @Override public <T> T acceptBinary(BinaryVisitor<T> visitor, Element other) { return other.acceptBinaryRight(visitor, this); } public <T> T acceptBinaryRight(BinaryVisitor<T> visitor, NumberElement left) { return visitor.visit(left, this); } public <T> T acceptBinaryRight(BinaryVisitor<T> visitor, StringElement left) { return visitor.visit(left, this); } } // 二元访问者接口 interface BinaryVisitor<T> { T visit(NumberElement a, NumberElement b); T visit(NumberElement a, StringElement b); T visit(StringElement a, NumberElement b); T visit(StringElement a, StringElement b); }
加法访问者实现
class AddVisitor implements BinaryVisitor<Object> { @Override public Object visit(NumberElement a, NumberElement b) { return a.getValue() + b.getValue(); } @Override public Object visit(NumberElement a, StringElement b) { return String.valueOf(a.getValue()) + b.getValue(); } @Override public Object visit(StringElement a, NumberElement b) { return a.getValue() + String.valueOf(b.getValue()); } @Override public Object visit(StringElement a, StringElement b) { return a.getValue() + b.getValue(); } }
这种方式把类型组合的逻辑集中在访问者里,元素类只需要处理基础的分派逻辑,比你原来的方案简洁不少。
2. 策略模式+类型注册表(低耦合首选)
如果你的场景里类型或操作会频繁新增,那用策略模式配合类型注册表会更灵活——完全不用修改元素类,新增操作或类型只需要添加新的策略类并注册即可。
实现示例
// 二元操作策略接口 interface BinaryOperationStrategy<T> { T execute(Element a, Element b); } // 数字加法策略 class NumberAddStrategy implements BinaryOperationStrategy<Integer> { @Override public Integer execute(Element a, Element b) { return ((NumberElement)a).getValue() + ((NumberElement)b).getValue(); } } // 数字+字符串拼接策略 class NumberStringAddStrategy implements BinaryOperationStrategy<String> { @Override public String execute(Element a, Element b) { return String.valueOf(((NumberElement)a).getValue()) + ((StringElement)b).getValue(); } } // 策略注册表 class OperationRegistry { private static final Map<ClassPair, BinaryOperationStrategy<?>> STRATEGIES = new HashMap<>(); // 静态注册所有支持的操作组合 static { STRATEGIES.put(new ClassPair(NumberElement.class, NumberElement.class), new NumberAddStrategy()); STRATEGIES.put(new ClassPair(NumberElement.class, StringElement.class), new NumberStringAddStrategy()); // 注册其他组合... } @SuppressWarnings("unchecked") public static <T> T execute(Element a, Element b) { ClassPair key = new ClassPair(a.getClass(), b.getClass()); BinaryOperationStrategy<T> strategy = (BinaryOperationStrategy<T>) STRATEGIES.get(key); if (strategy == null) { throw new UnsupportedOperationException("不支持的类型组合: " + a.getClass() + " + " + b.getClass()); } return strategy.execute(a, b); } // 辅助类,用作Map的键 private static class ClassPair { private final Class<?> leftType; private final Class<?> rightType; public ClassPair(Class<?> leftType, Class<?> rightType) { this.leftType = leftType; this.rightType = rightType; } // 必须重写equals和hashCode才能作为Map键 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ClassPair classPair = (ClassPair) o; return leftType.equals(classPair.leftType) && rightType.equals(classPair.rightType); } @Override public int hashCode() { return Objects.hash(leftType, rightType); } } }
使用时直接调用OperationRegistry.execute(a, b)即可,完全解耦了元素和操作逻辑。
3. 利用现代语言的模式匹配(最简洁)
如果你的语言支持模式匹配(比如Java 17+、C#、Kotlin、Scala等),这可能是最简洁的方案——不用任何额外的接口或类,直接通过类型匹配处理不同组合:
Java 17+ 示例
public static Object add(Element a, Element b) { return switch ((a, b)) { case (NumberElement na, NumberElement nb) -> na.getValue() + nb.getValue(); case (NumberElement na, StringElement sb) -> String.valueOf(na.getValue()) + sb.getValue(); case (StringElement sa, NumberElement nb) -> sa.getValue() + String.valueOf(nb.getValue()); case (StringElement sa, StringElement sb) -> sa.getValue() + sb.getValue(); default -> throw new UnsupportedOperationException("不支持的操作"); }; }
这种方式代码直观易懂,适合类型和操作都相对固定的场景,几乎没有冗余代码。
总结
- 如果类型和操作都较少,二元访问者模式足够简洁且符合访问者的设计思想;
- 如果类型或操作频繁变动,策略+注册表模式是低耦合的首选;
- 如果用的是现代语言,模式匹配是最直接的解决方案。
内容的提问来源于stack exchange,提问作者Eliott
相关产品推荐
相关产品推荐

