Java策略模式 源码运用 解析:算法切换的运行机制与实战应用
一、源码级解析: 模式在JDK中的经典应用
1.1 Comparator + Arrays.sort ------ 最纯正的策略模式
策略模式最经典的体现之一就是 Comparator 接口和 Arrays.sort(T[], Comparator<T>) 方法。
角色映射:
| 角色 | JDK源码对应 | 说明 |
|---|---|---|
| 抽象策略(Strategy) | Comparator<T> 接口 |
定义 compare(T o1, T o2) 抽象方法-1 |
| 具体策略(ConcreteStrategy) | 用户实现 Comparator 的匿名类/Lambda |
封装具体的排序规则 |
| 环境/上下文(Context) | Arrays 类 + TimSort 类 |
持有策略引用并调用委派-10 |
源码解析1:Arrays.sort 方法
// java.util.Arrays
public static <T> void sort(T[] a, Comparator<? super T> c) {
if (c == null) {
sort(a); // 没有提供策略时,使用默认自然排序
} else {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a, c);
else
// 核心:将策略对象c传递给TimSort使用
TimSort.sort(a, 0, a.length, c, null, 0, 0);
}
}
这里 Arrays 就是环境角色 ,它不关心具体排序算法如何比较,而是拿着传入的 Comparator 策略,一路向下委派-1。
源码解析2:TimSort 真正使用策略的地方
// java.util.TimSort
private static <T> int countRunAndMakeAscending(T[] a, int lo, int hi,
Comparator<? super T> c) {
// ...
// 真正调用策略方法的位置——在比较两个元素时调用Comparator的compare方法
if (c.compare(a[runHi++], a[lo]) < 0) {
while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) < 0)
runHi++;
reverseRange(a, lo, runHi);
} else {
while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) >= 0)
runHi++;
}
return runHi - lo;
}
countRunAndMakeAscending() 方法会调用 c.compare() 来比较相邻元素-10。这里完全体现多态委派 :TimSort.sort() 只知道 Comparator 接口,不关心你是升序、降序、按ID还是按姓名排序,具体比较逻辑通过多态动态绑定到你传入的实现类。
完整的运行流程示例:
public class StrategyInJDKDemo {
public static void main(String[] args) {
Integer[] arr = {5, 2, 8, 1, 9};
// 场景1:升序策略(Lambda形式,本质是匿名内部类)
Arrays.sort(arr, (a, b) -> a - b);
System.out.println("升序: " + Arrays.toString(arr)); // [1, 2, 5, 8, 9]
// 场景2:降序策略
Arrays.sort(arr, (a, b) -> b - a);
System.out.println("降序: " + Arrays.toString(arr)); // [9, 8, 5, 2, 1]
// 场景3:奇偶优先策略(偶数在前,奇数在后)
Arrays.sort(arr, (a, b) -> {
boolean aEven = a % 2 == 0;
boolean bEven = b % 2 == 0;
if (aEven == bEven) return a - b;
return aEven ? -1 : 1;
});
System.out.println("奇偶优先: " + Arrays.toString(arr));
}
}
运行机制总结 :Arrays.sort() 每次传入不同的 Comparator 策略实现,就能在完全无需修改Arrays源码 的情况下获得截然不同的排序行为-2。这就是策略模式最核心的价值------算法独立于调用者变化。
1.2 ThreadPoolExecutor拒绝策略------另一个典型应用
JDK中 ThreadPoolExecutor 的 RejectedExecutionHandler 也是策略模式的优秀实践-18。
// java.util.concurrent.ThreadPoolExecutor
public static class AbortPolicy implements RejectedExecutionHandler {
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " + e.toString());
}
}
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run(); // 谁提交谁执行
}
}
}
使用时:
// 通过策略模式设置不同的拒绝处理方式
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
new ThreadPoolExecutor.CallerRunsPolicy()); // 设置具体拒绝策略
二、运行机制拆解:"设置策略"到底发生了什么?
当客户端执行 context.setStrategy(new ConcreteStrategy()) 时,底层发生了以下过程:
┌─────────────────────────────────────────────────────────────────┐
│ 客户端(Client) │
│ context.setStrategy(new QuickSortStrategy()); │
│ │ │
│ ▼(指向堆中新建的QuickSortStrategy对象) │
└─────────────────┼───────────────────────────────────────────────┘
│ 引用赋值
▼
┌─────────────────────────────────────────────────────────────────┐
│ 上下文对象(Context) │
│ ┌─────────────────────┐ │
│ │ strategy : Strategy │ ────────(多态引用接口类型)────► QuickSortStrategy对象
│ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
│ 执行时调用 strategy.sort()
▼
┌─────────────────────────────────────────────────────────────────┐
│ JVM动态绑定机制(根据实际对象类型确定调用的方法) │
│ QuickSortStrategy.sort() 被调用 │
└─────────────────────────────────────────────────────────────────┘
这里存在三个关键机制:
-
接口引用接收具体对象 :
Context.strategy是接口类型Strategy,实际指向的是QuickSortStrategy实例。这是多态的基础。 -
Setter注入而非构造函数注入 :赋值
this.strategy = strategy后,引用指向新策略,旧的策略对象失去引用,GC自动回收(除非外部还持有引用)。 -
多态委派 :当
context.execute()调用strategy.doSomething()时,JVM 的动态绑定 会从实际对象类型(QuickSortStrategy)中找到对应方法执行。整个Context不需要知道具体策略是谁,只需要知道它能做什么(接口定义)。
三、实战场景Demo:电商多渠道支付系统
以电商支付场景展示策略模式的完整应用-25。
步骤1:策略接口 ------ 抽象策略
public interface PaymentStrategy {
void pay(double amount); // 执行支付
String getChannelName(); // 获取支付渠道名称
boolean supportsCurrency(String currency); // 渠道支持的币种
}
步骤2:具体策略实现 ------ 支付宝、微信、银行卡
// 支付宝支付策略
public class AlipayStrategy implements PaymentStrategy {
private String alipayAccount;
public AlipayStrategy(String alipayAccount) {
this.alipayAccount = alipayAccount;
}
@Override
public void pay(double amount) {
System.out.printf("[支付宝] 账号 %s 正在支付 ¥%.2f%n", alipayAccount, amount);
// 实际场景:调用支付宝开放平台API,涉及签名、异步通知等逻辑
}
@Override
public String getChannelName() { return "支付宝"; }
@Override
public boolean supportsCurrency(String currency) {
return "CNY".equals(currency);
}
}
// 微信支付策略
public class WechatPayStrategy implements PaymentStrategy {
private String openId;
public WechatPayStrategy(String openId) {
this.openId = openId;
}
@Override
public void pay(double amount) {
System.out.printf("[微信支付] 用户 %s 正在支付 ¥%.2f%n", openId, amount);
}
@Override
public String getChannelName() { return "微信支付"; }
@Override
public boolean supportsCurrency(String currency) {
return "CNY".equals(currency);
}
}
// 银行卡支付策略
public class CreditCardStrategy implements PaymentStrategy {
private String cardNo;
private String cvv;
public CreditCardStrategy(String cardNo, String cvv) {
this.cardNo = cardNo;
this.cvv = cvv;
}
@Override
public void pay(double amount) {
System.out.printf("[银行卡] 卡号 %s 正在支付 ¥%.2f%n", maskCardNumber(cardNo), amount);
// 实际场景:对接银联/国际卡组织,涉及3D验证等安全校验
}
private String maskCardNumber(String cardNo) {
if (cardNo.length() <= 4) return cardNo;
return "****" + cardNo.substring(cardNo.length() - 4);
}
@Override
public String getChannelName() { return "银行卡"; }
@Override
public boolean supportsCurrency(String currency) {
return true; // 银行卡支持多种币种
}
}
步骤3:上下文------订单支付管理器
// 上下文:持有策略引用,对外提供统一接口
public class OrderPaymentContext {
private PaymentStrategy strategy;
private List<Double> paymentHistory = new ArrayList<>(); // 支付历史
// 设置策略(运行时动态改变支付方式)
public void setStrategy(PaymentStrategy strategy) {
if (strategy == null) {
throw new IllegalArgumentException("Payment strategy cannot be null");
}
this.strategy = strategy;
System.out.println("已切换到: " + strategy.getChannelName());
}
// 执行支付(将支付委派给具体策略)
public boolean executePayment(double amount) {
if (strategy == null) {
System.err.println("请先设置支付方式");
return false;
}
// 执行策略方法(这里由JVM多态决定具体调用哪个策略的pay())
strategy.pay(amount);
paymentHistory.add(amount);
System.out.printf("支付 %.2f 成功,本次使用 %s%n", amount, strategy.getChannelName());
return true;
}
// 获取支付总额
public double getTotalPaid() {
return paymentHistory.stream().mapToDouble(Double::doubleValue).sum();
}
}
步骤4:客户端------用户下单场景
public class PaymentClient {
public static void main(String[] args) {
OrderPaymentContext order = new OrderPaymentContext();
// 场景1:用户选择支付宝支付
order.setStrategy(new AlipayStrategy("alice@alipay.com"));
order.executePayment(299.9);
// 场景2:同一订单,结算尾款时切换为微信支付(动态切换策略)
order.setStrategy(new WechatPayStrategy("wxid_abc123"));
order.executePayment(100.0);
// 场景3:国际订单,切换到支持多币种的银行卡支付
order.setStrategy(new CreditCardStrategy("6222123412345678", "123"));
order.executePayment(50.0);
System.out.println("订单累计支付总额: ¥" + order.getTotalPaid());
}
}
输出:
已切换到: 支付宝
[支付宝] 账号 alice@alipay.com 正在支付 ¥299.90
支付 299.90 成功,本次使用 支付宝
已切换到: 微信支付
[微信支付] 用户 wxid_abc123 正在支付 ¥100.00
支付 100.00 成功,本次使用 微信支付
已切换到: 银行卡
[银行卡] 卡号 ****5678 正在支付 ¥50.00
支付 50.00 成功,本次使用 银行卡
订单累计支付总额: ¥449.9
四、策略模式 vs 状态模式
两者类结构极其相似,但意图截然不同-。
| 对比维度 | 策略模式 | 状态模式 |
|---|---|---|
| 核心意图 | 封装一组可互换的算法 | 封装对象的状态及状态对应的行为 |
| 切换主体 | 客户端主动选择并设置具体策略 | 状态对象自身决定下一个状态(通常在状态类内部实现迁移) |
| 策略/状态间关系 | 平等、独立、可任意互换 | 通常存在流转关系(如A→B→C→A形成一个闭环) |
| 客户端感知 | 需要知道有哪些策略可用,并主动选择 | 无需关心状态流转逻辑,上下文自动变化 |
| 举例 | 支付方式、排序规则、压缩算法 | 订单状态(待支付→已支付→已发货→已完成)、TCP连接状态 |
| 口诀 | 关注"怎么做",行为平等可互换 | 关注"是什么状态",行为与状态绑定且存在迁移 |
类图层面,二者结构完全相同(都有Context持有Strategy/State接口),因此常被混淆。区分方法看切换动机:
strategy.setStrategy()由客户端主动调用;而状态模式中,Context往往只需要调用handle(),状态迁移由状态类内部完成。
五、总结
策略模式的核心是多态 + 委派 :上下文不实现算法,只是将算法执行委派给一个可插拔的、实现了统一接口的策略对象。JDK源码中的 Comparator 就是最典范的应用:Arrays.sort() 完全依赖你传入的比较策略来完成排序,无论你想按什么规则排,都不需要改动 Arrays 的代码-1。
在实际项目中,当遇到:
-
大量的
if-else判断选择算法 -
业务上需要对一组可互换的算法进行管理
-
希望新增算法时不影响原有代码
优先考虑策略模式 。而且随着Java 8+的函数式特性,配合Map<String, Function>或Spring的Map<String, Strategy>自动注入,策略模式在消除if-else 方面的威力更加强大-2-。