深入理解策略模式:灵活切换算法的行为型设计模式
引言
在软件开发中,我们经常会遇到需要根据不同情况执行不同算法的场景。例如,一个电商系统需要支持多种支付方式(支付宝、微信、银行卡),一个排序功能需要支持多种排序算法(快速排序、归并排序、冒泡排序)。如果使用大量的 if-else 或 switch 语句来处理,代码会变得臃肿且难以维护。
这时,**策略模式(Strategy Pattern)**就派上用场了。策略模式是行为型设计模式之一,它将一组算法封装成独立的类,使它们可以相互替换,从而让算法的变化不会影响到使用算法的客户端。
什么是策略模式
策略模式的核心思想非常简单:定义一系列算法,将每个算法封装到独立的类中,并使它们可以相互替换。
更通俗地说,策略模式就像是一个”工具箱”,里面放着不同的工具(策略)。当你需要完成某项任务时,可以根据具体情况选择最合适的工具来使用,而不需要关心工具内部是如何工作的。
模式结构
策略模式主要包含三种角色:
- 策略接口(Strategy Interface):定义所有策略的公共接口,客户端使用这个接口来调用具体策略
- 具体策略(Concrete Strategy):实现策略接口的具体算法实现类
- 上下文(Context):持有一个策略的引用,负责调用策略的方法
实战示例:支付系统
让我们通过一个支付系统的例子来深入理解策略模式。
场景描述
假设我们需要开发一个支付系统,支持多种支付方式:支付宝、微信支付、银行卡支付。每种支付方式的实现逻辑不同,但都需要执行支付操作。
代码实现
1. 定义策略接口
/**
* 支付策略接口
*/
public interface PaymentStrategy {
/**
* 执行支付
* @param amount 支付金额
* @return 支付结果
*/
boolean pay(double amount);
/**
* 获取支付方式名称
* @return 支付方式名称
*/
String getPaymentName();
}
2. 实现具体策略类
支付宝支付策略:
public class AlipayStrategy implements PaymentStrategy {
@Override
public boolean pay(double amount) {
System.out.println("使用支付宝支付:" + amount + " 元");
System.out.println("正在调用支付宝API...");
System.out.println("支付宝支付成功!");
return true;
}
@Override
public String getPaymentName() {
return "支付宝";
}
}
微信支付策略:
public class WechatPayStrategy implements PaymentStrategy {
@Override
public boolean pay(double amount) {
System.out.println("使用微信支付:" + amount + " 元");
System.out.println("正在调用微信支付API...");
System.out.println("微信支付成功!");
return true;
}
@Override
public String getPaymentName() {
return "微信支付";
}
}
银行卡支付策略:
public class BankCardStrategy implements PaymentStrategy {
private String cardNumber;
private String password;
public BankCardStrategy(String cardNumber, String password) {
this.cardNumber = cardNumber;
this.password = password;
}
@Override
public boolean pay(double amount) {
System.out.println("使用银行卡支付:" + amount + " 元");
System.out.println("卡号:" + maskCardNumber(cardNumber));
System.out.println("正在验证银行卡信息...");
System.out.println("银行卡支付成功!");
return true;
}
@Override
public String getPaymentName() {
return "银行卡";
}
private String maskCardNumber(String cardNumber) {
if (cardNumber == null || cardNumber.length() < 8) {
return cardNumber;
}
return cardNumber.substring(0, 4) + "****" + cardNumber.substring(cardNumber.length() - 4);
}
}
3. 创建上下文类
public class PaymentContext {
private PaymentStrategy paymentStrategy;
public PaymentContext(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
/**
* 设置支付策略
*/
public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
/**
* 执行支付
*/
public boolean executePayment(double amount) {
if (paymentStrategy == null) {
throw new IllegalStateException("未设置支付策略");
}
System.out.println("======== 支付开始 ========");
System.out.println("支付方式:" + paymentStrategy.getPaymentName());
boolean result = paymentStrategy.pay(amount);
System.out.println("======== 支付结束 ========");
return result;
}
}
4. 客户端调用
public class PaymentDemo {
public static void main(String[] args) {
// 创建支付上下文
PaymentContext context = new PaymentContext(new AlipayStrategy());
context.executePayment(100.0);
System.out.println("\n");
// 切换支付方式
context.setPaymentStrategy(new WechatPayStrategy());
context.executePayment(200.0);
System.out.println("\n");
// 使用银行卡支付
context.setPaymentStrategy(new BankCardStrategy("6222021234567890", "123456"));
context.executePayment(300.0);
}
}
输出结果:
======== 支付开始 ========
支付方式:支付宝
使用支付宝支付:100.0 元
正在调用支付宝API...
支付宝支付成功!
======== 支付结束 ========
======== 支付开始 ========
支付方式:微信支付
使用微信支付:200.0 元
正在调用微信支付API...
微信支付成功!
======== 支付结束 ========
======== 支付开始 ========
支付方式:银行卡
使用银行卡支付:300.0 元
卡号:6222****7890
正在验证银行卡信息...
银行卡支付成功!
======== 支付结束 ========
代码解析
- 策略接口:定义了统一的支付接口,所有支付方式都遵循这个接口
- 具体策略类:每个支付方式独立实现,互不影响
- 上下文类:负责管理策略,提供统一的调用入口
- 客户端:可以根据需要动态切换支付方式,无需修改调用代码
进阶示例:排序算法策略
让我们看一个更经典的例子:排序算法的策略模式实现。
定义策略接口
public interface SortStrategy {
/**
* 排序算法
*/
<T extends Comparable<T>> void sort(T[] array);
/**
* 获取算法名称
*/
String getName();
}
实现具体策略
快速排序:
public class QuickSortStrategy implements SortStrategy {
@Override
public <T extends Comparable<T>> void sort(T[] array) {
if (array == null || array.length == 0) {
return;
}
quickSort(array, 0, array.length - 1);
}
private <T extends Comparable<T>> void quickSort(T[] array, int left, int right) {
if (left < right) {
int pivotIndex = partition(array, left, right);
quickSort(array, left, pivotIndex - 1);
quickSort(array, pivotIndex + 1, right);
}
}
private <T extends Comparable<T>> int partition(T[] array, int left, int right) {
T pivot = array[right];
int i = left - 1;
for (int j = left; j < right; j++) {
if (array[j].compareTo(pivot) <= 0) {
i++;
swap(array, i, j);
}
}
swap(array, i + 1, right);
return i + 1;
}
private <T> void swap(T[] array, int i, int j) {
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
@Override
public String getName() {
return "快速排序";
}
}
冒泡排序:
public class BubbleSortStrategy implements SortStrategy {
@Override
public <T extends Comparable<T>> void sort(T[] array) {
if (array == null || array.length == 0) {
return;
}
int n = array.length;
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (array[j].compareTo(array[j + 1]) > 0) {
swap(array, j, j + 1);
}
}
}
}
private <T> void swap(T[] array, int i, int j) {
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
@Override
public String getName() {
return "冒泡排序";
}
}
排序上下文
public class SortContext {
private SortStrategy sortStrategy;
public SortContext(SortStrategy sortStrategy) {
this.sortStrategy = sortStrategy;
}
public void setSortStrategy(SortStrategy sortStrategy) {
this.sortStrategy = sortStrategy;
}
public <T extends Comparable<T>> void executeSort(T[] array) {
System.out.println("使用算法:" + sortStrategy.getName());
long startTime = System.currentTimeMillis();
sortStrategy.sort(array);
long endTime = System.currentTimeMillis();
System.out.println("排序耗时:" + (endTime - startTime) + "ms");
}
}
使用示例
public class SortDemo {
public static void main(String[] args) {
Integer[] array = {64, 34, 25, 12, 22, 11, 90};
SortContext context = new SortContext(new QuickSortStrategy());
System.out.println("原始数组:" + Arrays.toString(array));
context.executeSort(array);
System.out.println("排序结果:" + Arrays.toString(array));
// 切换排序算法
Integer[] array2 = {64, 34, 25, 12, 22, 11, 90};
context.setSortStrategy(new BubbleSortStrategy());
context.executeSort(array2);
System.out.println("排序结果:" + Arrays.toString(array2));
}
}
策略工厂模式
在实际应用中,我们通常会结合工厂模式来创建策略对象,避免客户端直接创建具体策略类。
// 传统的静态代码块方式(不推荐):
/*
public class PaymentStrategyFactory {
private static final Map<String, PaymentStrategy> strategies = new HashMap<>();
static {
strategies.put("ALIPAY", new AlipayStrategy());
strategies.put("WECHAT", new WechatPayStrategy());
// 银行卡需要参数,不能预先创建
}
public static PaymentStrategy getStrategy(String type) {
PaymentStrategy strategy = strategies.get(type);
if (strategy == null) {
throw new IllegalArgumentException("不支持的支付方式:" + type);
}
return strategy;
}
public static PaymentStrategy getBankCardStrategy(String cardNumber, String password) {
return new BankCardStrategy(cardNumber, password);
}
public static void registerStrategy(String type, PaymentStrategy strategy) {
strategies.put(type, strategy);
}
}
*/
// Spring优化方式(推荐):
// 1. 策略工厂类
@Component
public class PaymentStrategyFactory {
@Autowired
private Map<String, PaymentStrategy> strategies;
public PaymentStrategy getStrategy(String type) {
PaymentStrategy strategy = strategies.get(type);
if (strategy == null) {
throw new IllegalArgumentException("不支持的支付方式:" + type);
}
return strategy;
}
public PaymentStrategy getBankCardStrategy(String cardNumber, String password) {
return new BankCardStrategy(cardNumber, password);
}
public Map<String, PaymentStrategy> getAllStrategies() {
return strategies;
}
}
// 2. 支付宝策略类
@Component("alipay")
public class AlipayStrategy implements PaymentStrategy {
@Override
public boolean pay(double amount) {
System.out.println("使用支付宝支付:" + amount + " 元");
System.out.println("正在调用支付宝API...");
System.out.println("支付宝支付成功!");
return true;
}
@Override
public String getPaymentName() {
return "支付宝";
}
}
// 3. 微信支付策略类
@Component("weixin")
public class WechatPayStrategy implements PaymentStrategy {
@Override
public boolean pay(double amount) {
System.out.println("使用微信支付:" + amount + " 元");
System.out.println("正在调用微信支付API...");
System.out.println("微信支付成功!");
return true;
}
@Override
public String getPaymentName() {
return "微信支付";
}
}
使用工厂:
// 传统方式调用
/*
public class PaymentWithFactoryDemo {
public static void main(String[] args) {
PaymentContext context = new PaymentContext(
PaymentStrategyFactory.getStrategy("ALIPAY")
);
context.executePayment(100.0);
}
}
*/
// Spring方式调用
@SpringBootApplication
public class PaymentApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(PaymentApplication.class, args);
PaymentStrategyFactory factory = context.getBean(PaymentStrategyFactory.class);
// 使用支付宝支付
PaymentStrategy alipayStrategy = factory.getStrategy("支付宝");
PaymentContext paymentContext = new PaymentContext();
paymentContext.setPaymentStrategy(alipayStrategy);
paymentContext.executePayment(100.0);
// 使用微信支付
PaymentStrategy wechatStrategy = factory.getStrategy("微信支付");
paymentContext.setPaymentStrategy(wechatStrategy);
paymentContext.executePayment(200.0);
// 使用银行卡支付
PaymentStrategy bankCardStrategy = factory.getBankCardStrategy("6222021234567890", "123456");
paymentContext.setPaymentStrategy(bankCardStrategy);
paymentContext.executePayment(300.0);
}
}
Spring优化优势:
- 无需手动维护静态代码块初始化strategies
- 通过@Component注解的value属性自定义beanName作为Map的key
- 新增策略时只需添加@Component注解,自动注册到Map中
- 利用Spring依赖注入,代码更简洁优雅
- 符合开闭原则和Spring最佳实践
结合枚举使用策略
在 Java 中,可以使用枚举来简化策略模式的实现:
public enum PaymentType implements PaymentStrategy {
ALIPAY {
@Override
public boolean pay(double amount) {
System.out.println("支付宝支付:" + amount + " 元");
return true;
}
@Override
public String getPaymentName() {
return "支付宝";
}
},
WECHAT {
@Override
public boolean pay(double amount) {
System.out.println("微信支付:" + amount + " 元");
return true;
}
@Override
public String getPaymentName() {
return "微信支付";
}
};
}
应用场景
策略模式在实际开发中应用非常广泛,以下是一些典型场景:
1. 支付系统
- 支持多种支付方式:支付宝、微信、银行卡、PayPal等
- 根据用户选择动态切换支付方式
- 新增支付方式时无需修改现有代码
2. 数据导出
- 支持多种导出格式:Excel、CSV、PDF、JSON
- 不同格式有不同的导出逻辑,但接口统一
3. 折扣计算
- 不同会员等级有不同的折扣策略
- 促销活动期间使用特殊的折扣策略
- 可以动态组合多种折扣策略
4. 路由算法
- 负载均衡:轮询、随机、加权轮询、一致性哈希
- 根据系统负载动态切换路由策略
5. 加密算法
- 支持多种加密算法:AES、DES、RSA
- 根据安全级别选择不同的加密策略
6. 数据验证
- 不同场景使用不同的验证规则
- 可配置的验证策略
7. 日志记录
- 支持多种日志输出:文件、数据库、消息队列
- 不同环境使用不同的日志策略
优点与缺点
优点
- 开闭原则:新增策略无需修改现有代码,只需新增一个策略类
- 避免条件语句:消除大量的 if-else 或 switch 语句,代码更清晰
- 提高封装性:每个策略独立封装,职责单一
- 易于扩展:新增策略不影响其他策略,扩展性强
- 运行时切换:可以在运行时动态切换算法,灵活性高
- 易于测试:每个策略可以独立测试,互不影响
缺点
- 客户端必须了解策略:客户端需要了解所有策略的区别,才能选择合适的策略
- 策略类数量增加:每个策略都是一个类,可能导致类数量增多
- 策略选择问题:如果策略数量很多,选择合适的策略可能变得复杂
- 通信开销:如果策略之间需要共享数据,可能增加通信开销
最佳实践
1. 使用工厂模式创建策略
避免客户端直接创建具体策略类,使用工厂模式统一管理策略的创建:
public class StrategyFactory {
private Map<String, Strategy> strategies;
public Strategy getStrategy(String type) {
return strategies.get(type);
}
}
2. 结合配置文件使用
将策略配置在外部文件中,实现动态加载:
# application.properties
payment.default.strategy=ALIPAY
payment.enabled.strategies=ALIPAY,WECHAT,BANKCARD
3. 使用依赖注入
在 Spring 等框架中,使用依赖注入自动装配策略:
@Component
public class PaymentContext {
private final Map<String, PaymentStrategy> strategies;
@Autowired
public PaymentContext(List<PaymentStrategy> strategyList) {
this.strategies = strategyList.stream()
.collect(Collectors.toMap(
PaymentStrategy::getPaymentName,
Function.identity()
));
}
}
4. 提供默认策略
为客户端提供默认策略,降低使用门槛:
public class PaymentContext {
private PaymentStrategy strategy = new AlipayStrategy(); // 默认策略
public void setStrategy(PaymentStrategy strategy) {
this.strategy = strategy;
}
}
5. 策略不可变
策略类应该是无状态的,或者状态不可变,确保线程安全:
public class QuickSortStrategy implements SortStrategy {
// 无成员变量,线程安全
}
6. 提供策略选择帮助
当策略较多时,提供策略选择的辅助方法:
public class PaymentContext {
public void showAvailableStrategies() {
System.out.println("可用的支付方式:");
strategies.forEach((key, value) -> {
System.out.println("- " + key + ": " + value.getDescription());
});
}
public PaymentStrategy recommendStrategy(double amount) {
// 根据金额推荐最合适的支付方式
if (amount > 10000) {
return getStrategy("BANKCARD");
} else {
return getStrategy("ALIPAY");
}
}
}
策略模式 vs 其他模式
vs 模板模式
- 策略模式:通过组合实现,整个算法可以替换
- 模板模式:通过继承实现,算法骨架固定,部分步骤可变
- 选择依据:如果算法整体变化,选策略模式;如果算法骨架不变,部分步骤变化,选模板模式
vs 状态模式
- 策略模式:客户端主动选择策略,策略之间相互独立
- 状态模式:状态自动切换,状态之间有转换关系
- 选择依据:如果算法相互独立,选策略模式;如果对象行为随状态变化,选状态模式
vs 命令模式
- 策略模式:关注算法的封装和切换
- 命令模式:关注请求的封装和撤销
- 选择依据:如果需要封装算法,选策略模式;如果需要封装请求,选命令模式
vs 简单工厂模式
- 策略模式:行为型模式,关注算法切换
- 简单工厂模式:创建型模式,关注对象创建
- 结合使用:策略模式常与工厂模式结合使用
实际应用案例
Spring 框架中的策略模式
ResourceLoader:
// Spring 的资源加载策略
ResourceLoader loader = new DefaultResourceLoader();
Resource resource1 = loader.getResource("classpath:application.properties");
Resource resource2 = loader.getResource("file:/path/to/file.txt");
Resource resource3 = loader.getResource("https://example.com/file.txt");
InstantiationStrategy:
// Spring Bean 实例化策略
public interface InstantiationStrategy {
Object instantiate(RootBeanDefinition bd, String beanName, BeanFactory owner);
}
JDK 中的策略模式
Comparator:
// 排序策略
Arrays.sort(array, (a, b) -> a.compareTo(b)); // 升序
Arrays.sort(array, (a, b) -> b.compareTo(a)); // 降序
ThreadPoolExecutor 的拒绝策略:
// 不同的拒绝策略
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize,
maxPoolSize,
keepAliveTime,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
组合模式:策略模式 + 责任链模式
在实际应用中,策略模式常与责任链模式结合使用:
public class DiscountCalculator {
private List<DiscountStrategy> strategies;
public double calculate(double originalPrice) {
double price = originalPrice;
for (DiscountStrategy strategy : strategies) {
price = strategy.apply(price);
}
return price;
}
public void addStrategy(DiscountStrategy strategy) {
strategies.add(strategy);
}
}
// 使用
DiscountCalculator calculator = new DiscountCalculator();
calculator.addStrategy(new MemberDiscountStrategy());
calculator.addStrategy(new PromotionDiscountStrategy());
calculator.addStrategy(new CouponDiscountStrategy());
double finalPrice = calculator.calculate(1000.0);
总结
策略模式是一种简单而强大的设计模式,它通过将算法封装成独立的类,实现了算法的定义、实现和使用的分离。在实际开发中,当我们需要根据不同情况执行不同算法,并且算法可能频繁变化时,策略模式是一个很好的选择。
核心要点:
- 策略接口定义算法的公共接口
- 具体策略类实现不同的算法
- 上下文类负责管理策略的切换
- 客户端可以动态选择和切换策略
- 结合工厂模式可以简化策略的创建和管理
使用场景:
- 多个类只在行为上有差异,可以使用策略模式动态选择行为
- 需要在运行时选择算法的不同变体
- 避免使用复杂的条件语句来选择行为
- 算法经常变化,需要灵活扩展
记住: 策略模式不是银弹,要根据实际场景选择是否使用。如果算法简单、变化不大,直接使用条件语句可能更简单明了。策略模式适用于算法复杂、变化频繁、需要灵活扩展的场景。
参考资料
- 《设计模式:可复用面向对象软件的基础》- GoF
- 《Head First 设计模式》
- 《Effective Java》(Third Edition)
- Spring Framework Documentation
- Java API Documentation
作者:leejie
日期:2026年
标签:Java、设计模式、策略模式、架构设计