双参数场景下的访问者模式实现:避免向下转型与扩展痛点
你这个问题真的说到点子上了——Java的单分派机制对付单参数多态很顺手,但双参数的类型组合匹配就有点棘手了。不过别担心,有两种靠谱的方案,其中一种完全符合你「新增子类只改一个类」的要求,而且基本不用做不安全的向下转型。
方案1:双重分派(传统访客模式变体)
这种方式遵循纯OOP的多态原则,适合子类扩展不频繁的场景。核心是通过两次多态调用(双重分派),让Java自动匹配到对应的处理逻辑,彻底摆脱instanceof和向下转型。
实现步骤:
- 在
FooBar抽象类中添加双重分派的入口方法,让每个子类负责触发与另一个对象的类型匹配 - 每个子类实现分派方法,调用对方对象的类型专属处理方法,完成第二次类型匹配
MethodClass只需维护各类组合的业务逻辑,无需再做类型判断
代码示例:
修改后的FooBar抽象类
public abstract class FooBar { abstract void methodA(); abstract void methodB(); // 双重分派的入口:让当前对象触发与另一个对象的类型匹配 public abstract void dispatchInteraction(FooBar other, MethodClass handler); // 为每个子类定义对应的处理方法 public abstract void handleFoo(Foo foo, MethodClass handler); public abstract void handleBar(Bar bar, MethodClass handler); }
Foo类的实现
public class Foo extends FooBar { @Override void methodA() { /* 原有业务逻辑 */ } @Override void methodB() { /* 原有业务逻辑 */ } @Override public void dispatchInteraction(FooBar other, MethodClass handler) { // 让对方对象处理当前Foo实例,触发第二次分派 other.handleFoo(this, handler); } @Override public void handleFoo(Foo foo, MethodClass handler) { // 触发MethodClass中Foo-Foo组合的逻辑 handler.doMethod(this, foo); } @Override public void handleBar(Bar bar, MethodClass handler) { // 触发MethodClass中Bar-Foo组合的逻辑 handler.doMethod(bar, this); } }
Bar类的实现
public class Bar extends FooBar { @Override void methodA() { /* 原有业务逻辑 */ } @Override void methodB() { /* 原有业务逻辑 */ } @Override public void dispatchInteraction(FooBar other, MethodClass handler) { other.handleBar(this, handler); } @Override public void handleFoo(Foo foo, MethodClass handler) { handler.doMethod(foo, this); } @Override public void handleBar(Bar bar, MethodClass handler) { handler.doMethod(this, bar); } }
简化后的MethodClass
class MethodClass { List<FooBar> list; void doMethod() { FooBar obj1 = /* 随机选取元素 */; FooBar obj2 = /* 随机选取元素 */; // 只需调用分派方法,无需任何类型判断 obj1.dispatchInteraction(obj2, this); } // 各类组合的业务逻辑保持不变 void doMethod(Foo foo1, Foo foo2) { foo1.methodA(); foo2.methodA(); } void doMethod(Foo foo, Bar bar) { foo.methodB(); bar.methodB(); } void doMethod(Bar bar1, Bar bar2) { bar1.methodA(); bar2.methodA(); } }
方案2:类型映射表(完美满足「仅修改一个类」需求)
如果你的核心诉求是新增FooBar子类时只改动MethodClass,那么这种基于类型映射的方案是最优解。它利用函数式接口+Map存储类型组合与处理逻辑的映射,所有扩展工作都集中在MethodClass中。
核心思路:
- 用
Map存储「(类型1, 类型2)」到「处理逻辑」的映射关系 - 处理双参数时,根据两个对象的实际类型从Map中取出对应的逻辑执行
- 新增子类时,仅需在Map中添加新的类型组合和对应的逻辑即可
代码示例:
第一步:定义函数式接口(封装处理逻辑)
@FunctionalInterface private interface PairHandler { void handle(FooBar first, FooBar second); }
第二步:修改MethodClass实现
import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang3.tuple.Pair; // 或者用Java 16+的Record自定义Pair类 class MethodClass { List<FooBar> list; // 存储类型对与处理逻辑的映射 private final Map<Pair<Class<? extends FooBar>, Class<? extends FooBar>>, PairHandler> handlerMap; public MethodClass() { handlerMap = new HashMap<>(); // 初始化现有类型组合的处理逻辑 registerHandler(Foo.class, Foo.class, (a, b) -> { ((Foo)a).methodA(); ((Foo)b).methodA(); }); registerHandler(Foo.class, Bar.class, (a, b) -> { ((Foo)a).methodB(); ((Bar)b).methodB(); }); registerHandler(Bar.class, Bar.class, (a, b) -> { ((Bar)a).methodA(); ((Bar)b).methodA(); }); // 处理反向组合(比如Bar-Foo复用Foo-Bar的逻辑) registerHandler(Bar.class, Foo.class, (a, b) -> handlerMap.get(Pair.of(Foo.class, Bar.class)).handle(b, a)); } // 封装注册逻辑,简化新增类型时的代码 private <T extends FooBar, U extends FooBar> void registerHandler(Class<T> type1, Class<U> type2, PairHandler handler) { handlerMap.put(Pair.of(type1, type2), handler); } void doMethod() { FooBar obj1 = /* 随机选取元素 */; FooBar obj2 = /* 随机选取元素 */; doMethod(obj1, obj2); } void doMethod(FooBar fooBar1, FooBar fooBar2) { Pair<Class<? extends FooBar>, Class<? extends FooBar>> key = Pair.of(fooBar1.getClass(), fooBar2.getClass()); PairHandler handler = handlerMap.get(key); if (handler != null) { handler.handle(fooBar1, fooBar2); } else { // 处理未定义的类型组合,比如抛出异常或执行默认逻辑 throw new IllegalArgumentException("No handler found for pair: " + key); } } // 原来的重载方法可以移除,因为逻辑已经移到Map的handler中了 }
新增子类时的操作:
假设你新增了Baz extends FooBar,只需在MethodClass的构造函数中添加几行代码:
// 添加Baz-Baz组合 registerHandler(Baz.class, Baz.class, (a, b) -> { ((Baz)a).methodA(); ((Baz)b).methodA(); }); // 添加Foo-Baz组合 registerHandler(Foo.class, Baz.class, (a, b) -> { ((Foo)a).methodB(); ((Baz)b).methodB(); }); // 添加Baz-Foo组合(复用Foo-Baz逻辑) registerHandler(Baz.class, Foo.class, (a, b) -> handlerMap.get(Pair.of(Foo.class, Baz.class)).handle(b, a)); // 同理添加Bar-Baz和Baz-Bar组合
完全不需要修改FooBar、Foo、Bar或Baz类的任何代码,完美符合你的需求!
方案对比
| 方案 | 优点 | 缺点 |
|---|---|---|
| 双重分派 | 纯OOP实现,无类型转换,类型绝对安全 | 新增子类时需修改多个类,扩展性较弱 |
| 类型映射表 | 新增子类仅修改MethodClass,扩展性极强 | 存在安全的类型转换(但由类型匹配保证安全),依赖Pair类或自定义Record |
内容的提问来源于stack exchange,提问作者DaWhiteSheep
相关产品推荐
相关产品推荐

