策略模式:算法族封装、Comparator策略、支付场景实战
策略模式,说白了就是「把算法拿出来,单独封装」。
我刚开始接触设计模式时,总觉得这玩意儿有点多余——算法写在类里不是挺好的吗?后来在一个支付项目里被坑惨了,才明白策略模式有多重要。
嗯,咱们今天就把这个模式彻底讲透。
什么是策略模式?
策略模式的核心思想很简单:定义一组算法,把它们各自封装起来,让它们可以互相替换。
打个比方。你出门去公司,可以走路、骑车、坐地铁、打车。这些就是不同的「策略」。你不需要改自己的身份,只需要换一种出行方式就行。
在代码里也是一样。一个对象的行为,可以在运行时动态切换。不用改类本身,不用写一堆 if-else。
策略模式三要素:
- 策略接口:定义算法的统一规范
- 具体策略:实现接口的各种算法
- 上下文:持有策略引用,负责调用
为什么需要策略模式?
我见过太多代码,一个方法里写了十几个 if-else。每次加新功能,就要改这个方法。改着改着,方法就变成了几百行的怪物。
你想想看,这种代码谁敢动?
策略模式就是来解决这个问题的。它遵循了开闭原则——对扩展开放,对修改关闭。加新策略,不需要改原有代码。
我在项目中遇到过最典型的场景:支付渠道对接。微信、支付宝、银联、PayPal……每个渠道的对接逻辑都不一样。如果用 if-else,每次加渠道都要改核心代码。用策略模式,加一个渠道就是加一个策略类,清爽得很。
策略模式的结构
先看一张图,把整体结构理清楚:
这张图你看懂了吗?上下文不直接依赖具体策略,只依赖策略接口。具体策略可以随便换,上下文不用改一行代码。
实战一: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 或 switch-case
- 算法的选择需要由客户端决定,或者需要在运行时动态切换
- 算法之间相互独立,没有共享状态
举个例子:电商的促销活动。满减、打折、赠品、包邮……每种促销方式都是一个策略。加新活动,加新策略类就行,不会影响已有的促销逻辑。
小结
策略模式,说白了就是把「变」和「不变」分开。不变的是上下文的调用逻辑,变的是具体的算法实现。
我在项目中用策略模式最多的场景就是支付和促销。这两个场景的共同特点是:业务规则经常变化,而且变化的方向不可预测。用策略模式,每次变化只需要加一个类,不改原有代码,测试也只需要测新策略。
嗯,这就是策略模式的精髓。代码写得好不好,不在于用了多少设计模式,而在于能不能应对变化。策略模式,就是帮你应对变化的一把好手。
一句话总结:把算法封装成策略,让上下文只依赖接口不依赖实现。这样,加新算法就像加新零件一样简单。