1. 概述

双分派(Double Dispatch) 是一种根据接收者(receiver)和参数类型动态决定调用哪个方法的技术术语。

很多开发者会将双分派和策略模式(Strategy Pattern)混淆。Java 并不原生支持双分派,但我们可以通过一些技巧来模拟这种行为,最常见的是使用访问者模式(Visitor Pattern)。

在本教程中,我们将通过领域驱动设计(DDD)的上下文,展示双分派的使用场景,并解释它与策略模式的区别。


2. 双分派详解

在讲解双分派之前,我们先回顾一下 Java 中的单分派(Single Dispatch)机制。

2.1. 单分派(Single Dispatch)

单分派是根据接收者(对象)的运行时类型来决定调用哪个方法。 这其实就是 Java 中多态的体现。

举个例子,我们定义一个折扣策略接口:

public interface DiscountPolicy {
    double discount(Order order);
}

它有两个实现:

  • 固定折扣策略:
public class FlatDiscountPolicy implements DiscountPolicy {
    @Override
    public double discount(Order order) {
        return 0.01;
    }
}
  • 根据订单金额决定折扣:
public class AmountBasedDiscountPolicy implements DiscountPolicy {
    @Override
    public double discount(Order order) {
        if (order.totalCost().isGreaterThan(Money.of(CurrencyUnit.USD, 500.00))) {
            return 0.10;
        } else {
            return 0;
        }
    }
}

下面是一个测试示例,展示了单分派的行为:

@Test
void test() {
    DiscountPolicy flatPolicy = new FlatDiscountPolicy();
    DiscountPolicy amountPolicy = new AmountBasedDiscountPolicy();
    Order orderWorth501Dollars = orderWorthNDollars(501);

    double flatDiscount = flatPolicy.discount(orderWorth501Dollars);
    double amountDiscount = amountPolicy.discount(orderWorth501Dollars);

    assertThat(flatDiscount).isEqualTo(0.01);
    assertThat(amountDiscount).isEqualTo(0.1);
}

总结:单分派只根据接收者类型决定调用哪个方法。


2.2. 双分派 vs 方法重载

双分派不仅考虑接收者类型,还考虑参数类型来决定调用哪个方法。

Java 不支持双分派,但可以通过访问者模式来模拟。

很多人误以为方法重载就是双分派,其实不是。方法重载是编译期行为,只根据变量的声明类型决定调用哪个方法。

举个例子:

public interface SpecialDiscountPolicy extends DiscountPolicy {
    double discount(SpecialOrder order);
}

public class SpecialOrder extends Order { ... }

如果我们这样写:

Order specialOrder = new SpecialOrder(...);
specialPolicy.discount(specialOrder);

即使实际类型是 SpecialOrder,调用的仍然是 discount(Order order) 方法。

这说明方法重载不是双分派!


2.3. 使用访问者模式模拟双分派

访问者模式允许我们在不修改原有类的情况下,为其添加新的行为。

举个例子:我们想为不同类型的订单生成不同的 HTML 展示。

定义接口:

public interface Visitable<V> {
    void accept(V visitor);
}

public interface OrderVisitor {
    void visit(Order order);
    void visit(SpecialOrder order);
}

让订单类实现 Visitable<OrderVisitor>

public class Order implements Visitable<OrderVisitor> {
    @Override
    public void accept(OrderVisitor visitor) {
        visitor.visit(this);
    }
}

public class SpecialOrder extends Order {
    @Override
    public void accept(OrderVisitor visitor) {
        visitor.visit(this);
    }
}

定义访问者实现:

public class HtmlOrderViewCreator implements OrderVisitor {
    private String html;

    public String getHtml() {
        return html;
    }

    @Override
    public void visit(Order order) {
        html = String.format("<p>Regular order total cost: %s</p>", order.totalCost());
    }

    @Override
    public void visit(SpecialOrder order) {
        html = String.format("<h1>Special Order</h1><p>total cost: %s</p>", order.totalCost());
    }
}

使用方式:

List<Order> orders = Arrays.asList(new Order(...), new SpecialOrder(...));
HtmlOrderViewCreator visitor = new HtmlOrderViewCreator();

orders.get(0).accept(visitor);
String regularHtml = visitor.getHtml();

orders.get(1).accept(visitor);
String specialHtml = visitor.getHtml();

这样就能根据订单类型动态调用不同的方法,模拟了双分派行为。

⚠️ 缺点:需要修改订单类,添加 accept 方法。


3. DDD 中的双分派实践

在 DDD 中,我们经常需要根据订单类型应用不同的折扣策略。这时候双分派就派上用场了。

3.1. 折扣策略与策略模式

我们定义了 DiscountPolicy 接口,用于支持多种折扣策略:

public interface DiscountPolicy {
    double discount(Order order);
}

✅ 这其实就是策略模式的一种体现。

3.2. 双分派与折扣策略结合

我们希望在计算订单总价时,能够根据订单类型动态选择折扣策略。

例如:

public class Order {
    public Money totalCost(SpecialDiscountPolicy policy) {
        return totalCost().multipliedBy(1 - applyDiscountPolicy(policy), RoundingMode.HALF_UP);
    }

    protected double applyDiscountPolicy(SpecialDiscountPolicy policy) {
        return policy.discount(this);
    }
}

子类 SpecialOrder 重写该方法:

public class SpecialOrder extends Order {
    @Override
    protected double applyDiscountPolicy(SpecialDiscountPolicy policy) {
        return policy.discount(this);
    }
}

这样我们就能根据订单类型调用不同的折扣逻辑。

举个例子:

Order order = new SpecialOrder(...);
SpecialDiscountPolicy policy = new SpecialDiscountPolicy() {
    @Override
    public double discount(Order order) { return 0; }

    @Override
    public double discount(SpecialOrder order) {
        if (order.isEligibleForExtraDiscount()) return 0.20;
        return 0.10;
    }
};

Money cost = order.totalCost(policy);

这样就能避免类型转换和反射,优雅地实现基于订单类型的折扣计算。


4. 总结

在本文中,我们介绍了:

  • ✅ 单分派是 Java 多态的体现;
  • ❌ 方法重载不是双分派;
  • ✅ 双分派可以通过访问者模式模拟;
  • ✅ 在 DDD 中,双分派与策略模式结合可以优雅地实现复杂业务逻辑;
  • ✅ 避免类型转换,提升代码可读性和可维护性。

完整示例代码可在 GitHub 获取。



原始标题:Double Dispatch in DDD