You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

双参数场景下的访问者模式实现:避免向下转型与扩展痛点

你这个问题真的说到点子上了——Java的单分派机制对付单参数多态很顺手,但双参数的类型组合匹配就有点棘手了。不过别担心,有两种靠谱的方案,其中一种完全符合你「新增子类只改一个类」的要求,而且基本不用做不安全的向下转型。


方案1:双重分派(传统访客模式变体)

这种方式遵循纯OOP的多态原则,适合子类扩展不频繁的场景。核心是通过两次多态调用(双重分派),让Java自动匹配到对应的处理逻辑,彻底摆脱instanceof和向下转型。

实现步骤:

  1. 在FooBar抽象类中添加双重分派的入口方法,让每个子类负责触发与另一个对象的类型匹配
  2. 每个子类实现分派方法,调用对方对象的类型专属处理方法,完成第二次类型匹配
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 06:41:35