策略模式:算法族封装、Comparator策略、支付场景实战

策略模式,说白了就是「把算法拿出来,单独封装」。

我刚开始接触设计模式时,总觉得这玩意儿有点多余——算法写在类里不是挺好的吗?后来在一个支付项目里被坑惨了,才明白策略模式有多重要。

嗯,咱们今天就把这个模式彻底讲透。

什么是策略模式?

策略模式的核心思想很简单:定义一组算法,把它们各自封装起来,让它们可以互相替换

打个比方。你出门去公司,可以走路、骑车、坐地铁、打车。这些就是不同的「策略」。你不需要改自己的身份,只需要换一种出行方式就行。

在代码里也是一样。一个对象的行为,可以在运行时动态切换。不用改类本身,不用写一堆 if-else。

策略模式三要素:

  • 策略接口:定义算法的统一规范
  • 具体策略:实现接口的各种算法
  • 上下文:持有策略引用,负责调用

为什么需要策略模式?

我见过太多代码,一个方法里写了十几个 if-else。每次加新功能,就要改这个方法。改着改着,方法就变成了几百行的怪物。

你想想看,这种代码谁敢动?

策略模式就是来解决这个问题的。它遵循了开闭原则——对扩展开放,对修改关闭。加新策略,不需要改原有代码。

我在项目中遇到过最典型的场景:支付渠道对接。微信、支付宝、银联、PayPal……每个渠道的对接逻辑都不一样。如果用 if-else,每次加渠道都要改核心代码。用策略模式,加一个渠道就是加一个策略类,清爽得很。

策略模式的结构

先看一张图,把整体结构理清楚:

策略模式结构图 «接口» Strategy ConcreteStrategyA + execute() ConcreteStrategyB + execute() ConcreteStrategyC + execute() 实现 实现 实现 Context - strategy: Strategy 持有引用 上下文通过策略接口调用具体算法,运行时可以动态切换

这张图你看懂了吗?上下文不直接依赖具体策略,只依赖策略接口。具体策略可以随便换,上下文不用改一行代码。

实战一:Comparator策略

其实Java里早就内置了策略模式——Comparator接口就是典型的策略模式。

你想想看,Collections.sort()方法接受一个Comparator参数。不同的比较器就是不同的排序策略。

// 策略接口
public interface Comparator<T> {
    int compare(T o1, T o2);
}

// 具体策略:按年龄升序
public class AgeComparator implements Comparator<Person> {
    @Override
    public int compare(Person p1, Person p2) {
        return p1.getAge() - p2.getAge();
    }
}

// 具体策略:按姓名降序
public class NameComparator implements Comparator<Person> {
    @Override
    public int compare(Person p1, Person p2) {
        return p2.getName().compareTo(p1.getName());
    }
}

// 使用
List<Person> list = new ArrayList<>();
// ... 添加数据

// 按年龄排序
Collections.sort(list, new AgeComparator());

// 按姓名排序
Collections.sort(list, new NameComparator());

你看,排序的逻辑和集合本身解耦了。想换排序方式,传不同的策略就行。

我的习惯:在Java里,能用Lambda表达式的地方,尽量用Lambda。比如上面的例子,可以写成:

list.sort((p1, p2) -> p1.getAge() - p2.getAge());

这样连策略类都不用写了,代码更简洁。

实战二:支付场景

这才是策略模式的重头戏。我当年做电商平台,对接了七八个支付渠道。一开始没经验,代码写成这样:

public class PaymentService {
    public void pay(String channel, double amount) {
        if ("alipay".equals(channel)) {
            // 支付宝支付逻辑
            System.out.println("支付宝支付:" + amount);
        } else if ("wechat".equals(channel)) {
            // 微信支付逻辑
            System.out.println("微信支付:" + amount);
        } else if ("unionpay".equals(channel)) {
            // 银联支付逻辑
            System.out.println("银联支付:" + amount);
        } else {
            throw new IllegalArgumentException("不支持的支付渠道");
        }
    }
}

这段代码有什么问题?

  • 每次加新渠道,都要改PaymentService
  • 所有支付逻辑混在一起,难以维护
  • 测试困难,要测所有分支

用策略模式重构一下:

// 策略接口
public interface PaymentStrategy {
    void pay(double amount);
}

// 具体策略:支付宝
public class AlipayStrategy implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("支付宝支付:" + amount + "元");
        // 实际的支付宝对接逻辑
    }
}

// 具体策略:微信支付
public class WechatStrategy implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("微信支付:" + amount + "元");
        // 实际的微信对接逻辑
    }
}

// 具体策略:银联支付
public class UnionpayStrategy implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("银联支付:" + amount + "元");
        // 实际的银联对接逻辑
    }
}

// 上下文
public class PaymentContext {
    private PaymentStrategy strategy;

    public PaymentContext(PaymentStrategy strategy) {
        this.strategy = strategy;
    }

    public void setStrategy(PaymentStrategy strategy) {
        this.strategy = strategy;
    }

    public void executePayment(double amount) {
        strategy.pay(amount);
    }
}

// 使用
public class Client {
    public static void main(String[] args) {
        PaymentContext context = new PaymentContext(new AlipayStrategy());
        context.executePayment(100.0);

        // 切换到微信支付
        context.setStrategy(new WechatStrategy());
        context.executePayment(200.0);
    }
}

关键点:上下文只负责调用策略,不关心策略内部怎么实现。加新支付渠道,只需要新建一个策略类,实现PaymentStrategy接口即可。

策略模式 + 工厂模式

实际项目中,策略模式经常和工厂模式搭配使用。客户端不需要知道具体策略类,只需要传一个标识符。

public class PaymentStrategyFactory {
    private static Map<String, PaymentStrategy> strategies = new HashMap<>();

    static {
        strategies.put("alipay", new AlipayStrategy());
        strategies.put("wechat", new WechatStrategy());
        strategies.put("unionpay", new UnionpayStrategy());
    }

    public static PaymentStrategy getStrategy(String channel) {
        PaymentStrategy strategy = strategies.get(channel);
        if (strategy == null) {
            throw new IllegalArgumentException("不支持的支付渠道:" + channel);
        }
        return strategy;
    }
}

// 使用
PaymentStrategy strategy = PaymentStrategyFactory.getStrategy("alipay");
PaymentContext context = new PaymentContext(strategy);
context.executePayment(100.0);

这样客户端代码就更干净了。只需要知道渠道名称,不需要知道具体策略类。

策略模式的优缺点

优点 缺点
  • 符合开闭原则,扩展方便
  • 避免大量 if-else
  • 算法可以复用
  • 支持运行时切换
  • 策略类数量会增加
  • 客户端需要了解策略差异
  • 策略之间无法共享状态

我曾经踩过的坑:策略模式不是万能的。如果策略之间有很多公共逻辑,强行拆分会造成代码重复。这时候可以考虑模板方法模式,或者把公共逻辑提取到抽象类里。

另外,策略类如果无状态,可以做成单例,避免频繁创建对象。

什么时候用策略模式?

我个人总结了几条经验:

  • 一个行为有多种实现方式,而且这些方式可能会变化或扩展
  • 需要避免大量的条件判断,尤其是 if-else 或 switch-case
  • 算法的选择需要由客户端决定,或者需要在运行时动态切换
  • 算法之间相互独立,没有共享状态

举个例子:电商的促销活动。满减、打折、赠品、包邮……每种促销方式都是一个策略。加新活动,加新策略类就行,不会影响已有的促销逻辑。

小结

策略模式,说白了就是把「变」和「不变」分开。不变的是上下文的调用逻辑,变的是具体的算法实现。

我在项目中用策略模式最多的场景就是支付和促销。这两个场景的共同特点是:业务规则经常变化,而且变化的方向不可预测。用策略模式,每次变化只需要加一个类,不改原有代码,测试也只需要测新策略。

嗯,这就是策略模式的精髓。代码写得好不好,不在于用了多少设计模式,而在于能不能应对变化。策略模式,就是帮你应对变化的一把好手。

一句话总结:把算法封装成策略,让上下文只依赖接口不依赖实现。这样,加新算法就像加新零件一样简单。

公众号:蓝海资料掘金营,微信 deep3321