深入理解设计模式之策略模式(Strategy Pattern)
本文全面解析策略模式的概念、实现与应用,通过丰富的Java代码示例带你掌握这一常用设计模式。
目录
- 一、引言
- 二、什么是策略模式
- 三、策略模式的结构
- 四、策略模式的实现
- 五、策略模式的优缺点
- 六、策略模式的应用场景
- 七、进阶应用示例
- 八、策略模式与其他模式的对比
- 九、在实际框架中的应用
- 十、最佳实践与注意事项
- 十一、总结
一、引言
在软件开发过程中,我们经常会遇到这样的场景:同一个功能有多种实现方式,需要根据不同的条件选择不同的算法或行为。如果使用大量的if-else或switch语句来处理这些分支逻辑,代码会变得臃肿、难以维护,并且违背了开闭原则(对扩展开放,对修改关闭)。
策略模式(Strategy Pattern)正是为了解决这类问题而诞生的。它将算法封装成独立的类,使得它们可以相互替换,让算法的变化独立于使用算法的客户端。这种模式在实际开发中应用广泛,比如支付方式选择、排序算法切换、价格计算策略等场景。
本文将从基础到进阶,全面讲解策略模式的原理、实现和应用,帮助你在实际项目中灵活运用这一重要的设计模式。
二、什么是策略模式
2.1 定义
**策略模式(Strategy Pattern)**是一种行为型设计模式,它定义了一系列算法,将每个算法封装起来,并使它们可以相互替换。策略模式让算法独立于使用它的客户端而变化。
简单来说,策略模式就是将可变的部分抽取出来,封装成独立的策略类,然后在运行时动态地选择使用哪个策略。
2.2 核心思想
策略模式的核心思想可以概括为:
- 封装变化:将变化的算法或行为封装到独立的策略类中
- 面向接口编程:客户端面向策略接口编程,而不是具体的实现类
- 组合优于继承:通过组合的方式引入策略对象,而不是使用继承
- 运行时切换:可以在运行时动态地切换使用不同的策略
2.3 解决的问题
策略模式主要解决以下问题:
- 避免多重条件判断(if-else/switch)导致的代码臃肿
- 提高代码的可维护性和可扩展性
- 符合开闭原则,增加新策略时不需要修改原有代码
- 将算法的责任和实现分离,使代码更加清晰
三、策略模式的结构
3.1 UML类图
策略模式的结构比较简单,主要包含三个角色:
┌─────────────────┐
│ Context │
│ (上下文) │
├─────────────────┤
│ - strategy │────────┐
├─────────────────┤ │
│ + setStrategy() │ │
│ + executeStr... │ │
└─────────────────┘ │
│
▼
┌─────────────────┐
│ <<interface>> │
│ Strategy │
│ (抽象策略) │
├─────────────────┤
│ + algorithm() │
└─────────────────┘
△
│
┌────────────┼────────────┐
│ │ │
┌─────────┴──────┐ ┌──┴──────────┐ ┌┴────────────────┐
│ConcreteStrategyA│ │ConcreteStrategyB│ │ConcreteStrategyC│
│ (具体策略A) │ │ (具体策略B) │ │ (具体策略C) │
├─────────────────┤ ├─────────────────┤ ├─────────────────┤
│ + algorithm() │ │ + algorithm() │ │ + algorithm() │
└─────────────────┘ └─────────────────┘ └─────────────────┘
3.2 主要角色
-
Strategy(抽象策略)
- 定义所有支持的算法的公共接口
- Context使用这个接口来调用某个具体策略实现的算法
-
ConcreteStrategy(具体策略)
- 实现Strategy接口的具体算法
- 每个具体策略类封装了一种具体的算法或行为
-
Context(上下文)
- 维护一个对Strategy对象的引用
- 可以定义一个接口让Strategy访问它的数据
- 负责与客户端交互,将客户端的请求委托给Strategy对象
3.3 工作流程
- 客户端创建Context对象,并配置具体的策略对象
- 客户端通过Context调用业务方法
- Context将请求委托给当前的策略对象执行
- 策略对象完成具体的算法实现并返回结果
四、策略模式的实现
4.1 基础示例:支付方式选择
这是策略模式最经典的应用场景之一。假设一个电商系统需要支持多种支付方式:支付宝、微信、银联等。
4.1.1 定义抽象策略接口
/**
* 支付策略接口
*/
public interface PaymentStrategy {
/**
* 执行支付
* @param amount 支付金额
* @return 支付结果
*/
boolean pay(double amount);
/**
* 获取支付方式名称
* @return 支付方式名称
*/
String getPaymentName();
}
4.1.2 实现具体策略类
支付宝支付策略
/**
* 支付宝支付策略
*/
public class AlipayStrategy implements PaymentStrategy {
private String email;
private String password;
public AlipayStrategy(String email, String password) {
this.email = email;
this.password = password;
}
@Override
public boolean pay(double amount) {
System.out.println("=== 使用支付宝支付 ===");
System.out.println("账户: " + email);
System.out.println("支付金额: " + amount + "元");
// 模拟支付逻辑
if (verify()) {
System.out.println("支付宝支付成功!");
return true;
}
System.out.println("支付宝支付失败!");
return false;
}
private boolean verify() {
// 模拟验证逻辑
System.out.println("正在验证支付宝账户...");
return email != null && password != null;
}
@Override
public String getPaymentName() {
return "支付宝";
}
}
微信支付策略
/**
* 微信支付策略
*/
public class WechatPayStrategy implements PaymentStrategy {
private String openId;
public WechatPayStrategy(String openId) {
this.openId = openId;
}
@Override
public boolean pay(double amount) {
System.out.println("=== 使用微信支付 ===");
System.out.println("OpenID: " + openId);
System.out.println("支付金额: " + amount + "元");
// 模拟支付逻辑
if (verify()) {
System.out.println("微信支付成功!");
return true;
}
System.out.println("微信支付失败!");
return false;
}
private boolean verify() {
// 模拟验证逻辑
System.out.println("正在验证微信账户...");
return openId != null && !openId.isEmpty();
}
@Override
public String getPaymentName() {
return "微信支付";
}
}
银联支付策略
/**
* 银联支付策略
*/
public class UnionPayStrategy implements PaymentStrategy {
private String cardNumber;
private String cvv;
public UnionPayStrategy(String cardNumber, String cvv) {
this.cardNumber = cardNumber;
this.cvv = cvv;
}
@Override
public boolean pay(double amount) {
System.out.println("=== 使用银联支付 ===");
System.out.println("卡号: " + maskCardNumber());
System.out.println("支付金额: " + amount + "元");
// 模拟支付逻辑
if (verify()) {
System.out.println("银联支付成功!");
return true;
}
System.out.println("银联支付失败!");
return false;
}
private boolean verify() {
// 模拟验证逻辑
System.out.println("正在验证银联卡信息...");
return cardNumber != null && cvv != null;
}
private String maskCardNumber() {
// 隐藏卡号中间部分
if (cardNumber != null && cardNumber.length() > 8) {
return cardNumber.substring(0, 4) + "****" +
cardNumber.substring(cardNumber.length() - 4);
}
return cardNumber;
}
@Override
public String getPaymentName() {
return "银联";
}
}
4.1.3 创建上下文类
/**
* 支付上下文(购物车)
*/
public class ShoppingCart {
private PaymentStrategy paymentStrategy;
private double totalAmount;
public ShoppingCart() {
this.totalAmount = 0.0;
}
/**
* 添加商品
*/
public void addItem(String item, double price) {
System.out.println("添加商品: " + item + ", 价格: " + price + "元");
totalAmount += price;
}
/**
* 设置支付策略
*/
public void setPaymentStrategy(PaymentStrategy strategy) {
this.paymentStrategy = strategy;
}
/**
* 执行结账
*/
public void checkout() {
if (paymentStrategy == null) {
System.out.println("请先选择支付方式!");
return;
}
System.out.println("\n开始结账...");
System.out.println("总金额: " + totalAmount + "元");
System.out.println("支付方式: " + paymentStrategy.getPaymentName());
boolean success = paymentStrategy.pay(totalAmount);
if (success) {
System.out.println("订单完成!\n");
totalAmount = 0.0; // 清空购物车
} else {
System.out.println("订单失败,请重试!\n");
}
}
public double getTotalAmount() {
return totalAmount;
}
}
4.1.4 客户端使用示例
/**
* 客户端测试类
*/
public class PaymentDemo {
public static void main(String[] args) {
// 创建购物车
ShoppingCart cart = new ShoppingCart();
// 添加商品
cart.addItem("iPhone 15 Pro", 8999.00);
cart.addItem("AirPods Pro", 1999.00);
System.out.println("\n========== 场景1:使用支付宝支付 ==========");
cart.setPaymentStrategy(new AlipayStrategy("user@example.com", "password123"));
cart.checkout();
// 继续购物
cart.addItem("iPad Air", 4799.00);
cart.addItem("Apple Watch", 3199.00);
System.out.println("\n========== 场景2:使用微信支付 ==========");
cart.setPaymentStrategy(new WechatPayStrategy("wx_openid_12345"));
cart.checkout();
// 再次购物
cart.addItem("MacBook Pro", 14999.00);
System.out.println("\n========== 场景3:使用银联支付 ==========");
cart.setPaymentStrategy(new UnionPayStrategy("6222021234567890", "123"));
cart.checkout();
}
}
运行结果:
添加商品: iPhone 15 Pro, 价格: 8999.0元
添加商品: AirPods Pro, 价格: 1999.0元
========== 场景1:使用支付宝支付 ==========
开始结账...
总金额: 10998.0元
支付方式: 支付宝
=== 使用支付宝支付 ===
账户: user@example.com
支付金额: 10998.0元
正在验证支付宝账户...
支付宝支付成功!
订单完成!
添加商品: iPad Air, 价格: 4799.0元
添加商品: Apple Watch, 价格: 3199.0元
========== 场景2:使用微信支付 ==========
开始结账...
总金额: 7998.0元
支付方式: 微信支付
=== 使用微信支付 ===
OpenID: wx_openid_12345
支付金额: 7998.0元
正在验证微信账户...
微信支付成功!
订单完成!
添加商品: MacBook Pro, 价格: 14999.0元
========== 场景3:使用银联支付 ==========
开始结账...
总金额: 14999.0元
支付方式: 银联
=== 使用银联支付 ===
卡号: 6222****7890
支付金额: 14999.0元
正在验证银联卡信息...
银联支付成功!
订单完成!
4.2 进阶示例:价格计算策略
在电商系统中,不同的用户类型(普通用户、VIP用户、超级VIP)享有不同的折扣优惠,这也是策略模式的典型应用场景。
4.2.1 定义价格策略接口
/**
* 价格计算策略接口
*/
public interface PriceStrategy {
/**
* 计算价格
* @param originalPrice 原价
* @return 折后价
*/
double calculatePrice(double originalPrice);
/**
* 获取策略描述
*/
String getDescription();
}
4.2.2 实现具体价格策略
/**
* 普通用户策略 - 无折扣
*/
public class NormalPriceStrategy implements PriceStrategy {
@Override
public double calculatePrice(double originalPrice) {
return originalPrice;
}
@Override
public String getDescription() {
return "普通用户 - 原价";
}
}
/**
* VIP用户策略 - 9折
*/
public class VipPriceStrategy implements PriceStrategy {
private static final double DISCOUNT = 0.9;
@Override
public double calculatePrice(double originalPrice) {
return originalPrice * DISCOUNT;
}
@Override
public String getDescription() {
return "VIP用户 - 9折优惠";
}
}
/**
* 超级VIP策略 - 8折
*/
public class SuperVipPriceStrategy implements PriceStrategy {
private static final double DISCOUNT = 0.8;
@Override
public double calculatePrice(double originalPrice) {
return originalPrice * DISCOUNT;
}
@Override
public String getDescription() {
return "超级VIP - 8折优惠";
}
}
/**
* 促销活动策略 - 满减
*/
public class PromotionPriceStrategy implements PriceStrategy {
private double threshold; // 满减门槛
private double reduction; // 减免金额
public PromotionPriceStrategy(double threshold, double reduction) {
this.threshold = threshold;
this.reduction = reduction;
}
@Override
public double calculatePrice(double originalPrice) {
if (originalPrice >= threshold) {
return originalPrice - reduction;
}
return originalPrice;
}
@Override
public String getDescription() {
return String.format("促销活动 - 满%.0f减%.0f", threshold, reduction);
}
}
4.2.3 价格计算器上下文
/**
* 价格计算器
*/
public class PriceCalculator {
private PriceStrategy strategy;
public void setStrategy(PriceStrategy strategy) {
this.strategy = strategy;
}
public double calculate(double originalPrice) {
if (strategy == null) {
throw new IllegalStateException("未设置价格策略!");
}
return strategy.calculatePrice(originalPrice);
}
public void printPriceInfo(double originalPrice) {
System.out.println("策略: " + strategy.getDescription());
System.out.println("原价: ¥" + String.format("%.2f", originalPrice));
double finalPrice = calculate(originalPrice);
System.out.println("实付: ¥" + String.format("%.2f", finalPrice));
double saved = originalPrice - finalPrice;
if (saved > 0) {
System.out.println("节省: ¥" + String.format("%.2f", saved));
}
System.out.println();
}
}
4.2.4 客户端测试
public class PriceStrategyDemo {
public static void main(String[] args) {
PriceCalculator calculator = new PriceCalculator();
double originalPrice = 1000.0;
System.out.println("========== 商品原价: ¥" + originalPrice + " ==========\n");
// 普通用户
System.out.println(">>> 普通用户购买");
calculator.setStrategy(new NormalPriceStrategy());
calculator.printPriceInfo(originalPrice);
// VIP用户
System.out.println(">>> VIP用户购买");
calculator.setStrategy(new VipPriceStrategy());
calculator.printPriceInfo(originalPrice);
// 超级VIP
System.out.println(">>> 超级VIP用户购买");
calculator.setStrategy(new SuperVipPriceStrategy());
calculator.printPriceInfo(originalPrice);
// 促销活动
System.out.println(">>> 促销活动期间购买");
calculator.setStrategy(new PromotionPriceStrategy(500, 100));
calculator.printPriceInfo(originalPrice);
}
}
五、策略模式的优缺点
5.1 优点
-
符合开闭原则
- 增加新的策略不需要修改原有代码
- 只需新增策略类并实现策略接口即可
-
避免使用多重条件判断
- 消除了冗长的if-else或switch语句
- 代码更加清晰易读
-
提高算法的保密性和安全性
- 算法细节被封装在具体策略类中
- 外部无法直接访问算法实现
-
提高代码的复用性
- 策略类可以在不同的上下文中复用
- 不同的策略可以灵活组合
-
易于扩展和维护
- 每个策略都是独立的类
- 修改某个策略不影响其他策略
-
支持运行时切换算法
- 可以动态地改变对象的行为
- 增强了程序的灵活性
5.2 缺点
-
客户端必须知道所有的策略类
- 客户端需要了解各个策略的区别
- 才能选择合适的策略
-
策略类数量增多
- 每个策略都是一个类
- 可能导致类的数量急剧增加
-
增加了对象的数目
- 每个策略都需要创建对象
- 可能带来一定的性能开销
-
只适合扁平的算法结构
- 如果算法之间存在复杂的依赖关系
- 策略模式可能不是最佳选择
5.3 适用场景
策略模式适用于以下场景:
-
多个类只是在算法或行为上稍有不同
- 如不同的排序算法、压缩算法等
-
需要在运行时动态地选择算法
- 如根据用户类型选择不同的价格策略
-
算法需要自由切换
- 如不同的出行方式(驾车、公交、步行)
-
需要屏蔽算法规则
- 不希望客户端知道复杂的算法逻辑
-
存在大量条件语句
- 将各个分支封装成独立的策略类
六、策略模式的应用场景
6.1 文件压缩场景
不同的文件类型可能需要不同的压缩算法。
/**
* 压缩策略接口
*/
public interface CompressionStrategy {
void compress(String filePath);
}
/**
* ZIP压缩策略
*/
public class ZipCompressionStrategy implements CompressionStrategy {
@Override
public void compress(String filePath) {
System.out.println("使用ZIP算法压缩文件: " + filePath);
// 实际压缩逻辑
}
}
/**
* RAR压缩策略
*/
public class RarCompressionStrategy implements CompressionStrategy {
@Override
public void compress(String filePath) {
System.out.println("使用RAR算法压缩文件: " + filePath);
// 实际压缩逻辑
}
}
/**
* 7Z压缩策略
*/
public class SevenZipCompressionStrategy implements CompressionStrategy {
@Override
public void compress(String filePath) {
System.out.println("使用7Z算法压缩文件: " + filePath);
// 实际压缩逻辑
}
}
/**
* 文件压缩器
*/
public class FileCompressor {
private CompressionStrategy strategy;
public void setCompressionStrategy(CompressionStrategy strategy) {
this.strategy = strategy;
}
public void compressFile(String filePath) {
if (strategy == null) {
throw new IllegalStateException("请先设置压缩策略!");
}
strategy.compress(filePath);
}
}
6.2 出行方式选择
根据距离、时间等因素选择不同的出行方式。
/**
* 出行策略接口
*/
public interface TravelStrategy {
void travel(String start, String destination);
int estimateTime(int distance); // 估算时间(分钟)
}
/**
* 驾车策略
*/
public class DrivingStrategy implements TravelStrategy {
@Override
public void travel(String start, String destination) {
System.out.println("驾车前往:" + start + " -> " + destination);
}
@Override
public int estimateTime(int distance) {
// 假设平均速度40km/h
return distance * 60 / 40;
}
}
/**
* 公交策略
*/
public class BusStrategy implements TravelStrategy {
@Override
public void travel(String start, String destination) {
System.out.println("乘坐公交:" + start + " -> " + destination);
}
@Override
public int estimateTime(int distance) {
// 假设平均速度20km/h(含等待时间)
return distance * 60 / 20;
}
}
/**
* 步行策略
*/
public class WalkingStrategy implements TravelStrategy {
@Override
public void travel(String start, String destination) {
System.out.println("步行前往:" + start + " -> " + destination);
}
@Override
public int estimateTime(int distance) {
// 假设步行速度5km/h
return distance * 60 / 5;
}
}
/**
* 出行规划器
*/
public class TravelPlanner {
private TravelStrategy strategy;
public void setStrategy(TravelStrategy strategy) {
this.strategy = strategy;
}
public void planTrip(String start, String destination, int distance) {
System.out.println("\n=== 出行规划 ===");
System.out.println("起点: " + start);
System.out.println("终点: " + destination);
System.out.println("距离: " + distance + "公里");
strategy.travel(start, destination);
int time = strategy.estimateTime(distance);
System.out.println("预计用时: " + time + "分钟");
}
}
6.3 数据验证场景
不同的表单字段需要不同的验证规则。
/**
* 验证策略接口
*/
public interface ValidationStrategy {
boolean validate(String value);
String getErrorMessage();
}
/**
* 邮箱验证策略
*/
public class EmailValidationStrategy implements ValidationStrategy {
private static final String EMAIL_PATTERN =
"^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$";
@Override
public boolean validate(String value) {
return value != null && value.matches(EMAIL_PATTERN);
}
@Override
public String getErrorMessage() {
return "邮箱格式不正确";
}
}
/**
* 手机号验证策略
*/
public class PhoneValidationStrategy implements ValidationStrategy {
private static final String PHONE_PATTERN = "^1[3-9]\\d{9}$";
@Override
public boolean validate(String value) {
return value != null && value.matches(PHONE_PATTERN);
}
@Override
public String getErrorMessage() {
return "手机号格式不正确";
}
}
/**
* 密码强度验证策略
*/
public class PasswordValidationStrategy implements ValidationStrategy {
private int minLength;
public PasswordValidationStrategy(int minLength) {
this.minLength = minLength;
}
@Override
public boolean validate(String value) {
if (value == null || value.length() < minLength) {
return false;
}
// 检查是否包含字母和数字
boolean hasLetter = value.matches(".*[a-zA-Z].*");
boolean hasDigit = value.matches(".*\\d.*");
return hasLetter && hasDigit;
}
@Override
public String getErrorMessage() {
return "密码至少" + minLength + "位,且包含字母和数字";
}
}
/**
* 表单验证器
*/
public class FormValidator {
private Map<String, ValidationStrategy> validators = new HashMap<>();
public void addValidator(String fieldName, ValidationStrategy strategy) {
validators.put(fieldName, strategy);
}
public Map<String, String> validate(Map<String, String> formData) {
Map<String, String> errors = new HashMap<>();
for (Map.Entry<String, String> entry : formData.entrySet()) {
String fieldName = entry.getKey();
String value = entry.getValue();
ValidationStrategy strategy = validators.get(fieldName);
if (strategy != null && !strategy.validate(value)) {
errors.put(fieldName, strategy.getErrorMessage());
}
}
return errors;
}
}
七、进阶应用示例
7.1 结合工厂模式:策略工厂
当策略类较多时,可以结合工厂模式来创建策略对象,避免客户端直接依赖具体策略类。
/**
* 支付方式枚举
*/
public enum PaymentType {
ALIPAY,
WECHAT,
UNIONPAY,
CREDIT_CARD
}
/**
* 策略工厂
*/
public class PaymentStrategyFactory {
private static final Map<PaymentType, PaymentStrategy> strategies = new HashMap<>();
static {
// 预先创建策略对象(也可以使用懒加载)
strategies.put(PaymentType.ALIPAY,
new AlipayStrategy("default@example.com", "default_pwd"));
strategies.put(PaymentType.WECHAT,
new WechatPayStrategy("default_openid"));
strategies.put(PaymentType.UNIONPAY,
new UnionPayStrategy("0000000000000000", "000"));
}
/**
* 获取策略对象
*/
public static PaymentStrategy getStrategy(PaymentType type) {
PaymentStrategy strategy = strategies.get(type);
if (strategy == null) {
throw new IllegalArgumentException("不支持的支付方式: " + type);
}
return strategy;
}
/**
* 注册新策略
*/
public static void registerStrategy(PaymentType type, PaymentStrategy strategy) {
strategies.put(type, strategy);
}
}
/**
* 使用示例
*/
public class FactoryPatternDemo {
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();
cart.addItem("商品A", 100.0);
// 通过工厂获取策略,客户端不需要直接创建策略对象
PaymentStrategy strategy = PaymentStrategyFactory.getStrategy(PaymentType.ALIPAY);
cart.setPaymentStrategy(strategy);
cart.checkout();
}
}
通过工厂模式,客户端无需直接依赖具体的策略类,只需要通过工厂获取策略对象即可,进一步降低了耦合度。
7.2 结合注解和反射:自动注册策略
在Spring框架中,我们可以使用注解和反射来自动注册策略。
/**
* 策略标识注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface StrategyType {
String value(); // 策略类型标识
}
/**
* 使用注解标识策略
*/
@StrategyType("alipay")
public class AlipayStrategy implements PaymentStrategy {
// ... 实现代码
}
@StrategyType("wechat")
public class WechatPayStrategy implements PaymentStrategy {
// ... 实现代码
}
/**
* 策略注册器
*/
public class StrategyRegistry {
private static final Map<String, Class<? extends PaymentStrategy>> registry = new HashMap<>();
/**
* 扫描并注册策略
*/
public static void scanAndRegister(String packageName) {
// 使用反射扫描包下的所有类
// 这里简化演示,实际可以使用Spring的ClassPathScanningCandidateComponentProvider
Set<Class<?>> classes = findAllClassesInPackage(packageName);
for (Class<?> clazz : classes) {
if (PaymentStrategy.class.isAssignableFrom(clazz)) {
StrategyType annotation = clazz.getAnnotation(StrategyType.class);
if (annotation != null) {
registry.put(annotation.value(),
(Class<? extends PaymentStrategy>) clazz);
}
}
}
}
/**
* 根据类型获取策略实例
*/
public static PaymentStrategy getStrategy(String type) {
Class<? extends PaymentStrategy> clazz = registry.get(type);
if (clazz == null) {
throw new IllegalArgumentException("未找到策略: " + type);
}
try {
return clazz.getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException("创建策略实例失败", e);
}
}
private static Set<Class<?>> findAllClassesInPackage(String packageName) {
// 实际实现需要类加载器和文件扫描
// 这里仅作示例
return new HashSet<>();
}
}
7.3 策略链:组合多个策略
有时候我们需要按顺序应用多个策略,可以使用策略链模式。
/**
* 价格调整策略(支持链式调用)
*/
public interface PriceAdjustmentStrategy {
double adjust(double price);
}
/**
* 会员折扣策略
*/
public class MemberDiscountStrategy implements PriceAdjustmentStrategy {
private double discount;
public MemberDiscountStrategy(double discount) {
this.discount = discount;
}
@Override
public double adjust(double price) {
double adjustedPrice = price * discount;
System.out.println(String.format("应用会员折扣(%.0f%%): %.2f -> %.2f",
discount * 100, price, adjustedPrice));
return adjustedPrice;
}
}
/**
* 优惠券策略
*/
public class CouponStrategy implements PriceAdjustmentStrategy {
private double couponAmount;
public CouponStrategy(double couponAmount) {
this.couponAmount = couponAmount;
}
@Override
public double adjust(double price) {
double adjustedPrice = Math.max(0, price - couponAmount);
System.out.println(String.format("应用优惠券(-%.2f): %.2f -> %.2f",
couponAmount, price, adjustedPrice));
return adjustedPrice;
}
}
/**
* 满减策略
*/
public class FullReductionStrategy implements PriceAdjustmentStrategy {
private double threshold;
private double reduction;
public FullReductionStrategy(double threshold, double reduction) {
this.threshold = threshold;
this.reduction = reduction;
}
@Override
public double adjust(double price) {
if (price >= threshold) {
double adjustedPrice = price - reduction;
System.out.println(String.format("应用满减(满%.0f减%.0f): %.2f -> %.2f",
threshold, reduction, price, adjustedPrice));
return adjustedPrice;
}
System.out.println(String.format("未达到满减门槛(%.0f), 价格不变: %.2f",
threshold, price));
return price;
}
}
/**
* 策略链
*/
public class PriceStrategyChain {
private List<PriceAdjustmentStrategy> strategies = new ArrayList<>();
public PriceStrategyChain addStrategy(PriceAdjustmentStrategy strategy) {
strategies.add(strategy);
return this; // 支持链式调用
}
public double calculate(double originalPrice) {
System.out.println("\n========== 价格计算链 ==========");
System.out.println("原价: " + originalPrice);
double finalPrice = originalPrice;
for (PriceAdjustmentStrategy strategy : strategies) {
finalPrice = strategy.adjust(finalPrice);
}
System.out.println("最终价格: " + finalPrice);
System.out.println("节省: " + (originalPrice - finalPrice));
return finalPrice;
}
}
/**
* 使用示例
*/
public class StrategyChainDemo {
public static void main(String[] args) {
PriceStrategyChain chain = new PriceStrategyChain();
// 链式添加多个策略
chain.addStrategy(new MemberDiscountStrategy(0.95)) // 95折
.addStrategy(new FullReductionStrategy(500, 50)) // 满500减50
.addStrategy(new CouponStrategy(30)); // 优惠券30元
// 计算最终价格
double finalPrice = chain.calculate(600.0);
}
}
策略链模式的优势在于可以灵活组合多个策略,按顺序应用不同的价格调整规则,适用于需要多重优惠叠加的复杂场景。
八、策略模式与其他模式的对比
8.1 策略模式 vs 状态模式
相似点:
- 都是行为型模式
- 类图结构类似,都有上下文和抽象接口
- 都使用组合而非继承
区别:
| 维度 | 策略模式 | 状态模式 |
|---|---|---|
| 目的 | 封装算法,使算法可互换 | 封装状态,根据状态改变行为 |
| 切换方式 | 通常由客户端主动切换 | 通常由状态对象自己切换 |
| 关注点 | 算法的选择和执行 | 对象内部状态的变化 |
| 策略/状态感知 | 策略之间相互独立 | 状态之间可能相互依赖 |
// 策略模式:客户端主动切换
ShoppingCart cart = new ShoppingCart();
cart.setPaymentStrategy(new AlipayStrategy()); // 客户端切换
// 状态模式:对象内部切换
Order order = new Order();
order.pay(); // 内部自动从"待支付"切换到"已支付"状态
8.2 策略模式 vs 工厂模式
策略模式关注的是算法的封装和切换,而工厂模式关注的是对象的创建。两者经常结合使用:
// 使用工厂创建策略对象
PaymentStrategy strategy = PaymentStrategyFactory.create("alipay");
// 使用策略执行算法
strategy.pay(100.0);
8.3 策略模式 vs 模板方法模式
相似点:
- 都是行为型模式
- 都定义了算法的骨架
区别:
| 维度 | 策略模式 | 模板方法模式 |
|---|---|---|
| 实现方式 | 使用组合(委托) | 使用继承 |
| 算法结构 | 完全独立的算法 | 算法有固定骨架,部分步骤可变 |
| 灵活性 | 运行时可切换 | 编译时确定 |
| 粒度 | 整个算法可替换 | 只能替换算法中的某些步骤 |
// 策略模式:整个算法可替换
context.setStrategy(new AlgorithmA());
// 模板方法:只能重写部分方法
class ConcreteClass extends AbstractClass {
@Override
protected void step2() {
// 重写部分步骤
}
}
九、在实际框架中的应用
9.1 JDK中的策略模式
9.1.1 Comparator接口
Java中的Comparator接口就是策略模式的典型应用:
public class ComparatorExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("John", "Alice", "Bob", "Charlie");
// 策略1:按字母顺序排序
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}
});
System.out.println("字母顺序: " + names);
// 策略2:按长度排序
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return Integer.compare(s1.length(), s2.length());
}
});
System.out.println("长度顺序: " + names);
// 策略3:使用Lambda(Java 8+)
Collections.sort(names, (s1, s2) -> s2.compareTo(s1)); // 逆序
System.out.println("逆序: " + names);
}
}
9.1.2 ThreadPoolExecutor的拒绝策略
Java线程池的拒绝策略也是策略模式的应用:
// 不同的拒绝策略
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 4, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
new ThreadPoolExecutor.AbortPolicy() // 策略1:抛出异常
// new ThreadPoolExecutor.CallerRunsPolicy() // 策略2:调用者运行
// new ThreadPoolExecutor.DiscardPolicy() // 策略3:丢弃任务
// new ThreadPoolExecutor.DiscardOldestPolicy() // 策略4:丢弃最老的任务
);
9.2 Spring框架中的策略模式
9.2.1 Resource接口
Spring的Resource接口用于访问不同类型的资源:
// 不同的资源访问策略
Resource resource1 = new ClassPathResource("config.xml"); // 类路径资源
Resource resource2 = new FileSystemResource("/path/to/file"); // 文件系统资源
Resource resource3 = new UrlResource("http://example.com"); // URL资源
9.2.2 实战:Spring Boot中使用策略模式
/**
* 消息发送策略接口
*/
public interface MessageSendStrategy {
void send(String message, String recipient);
String getType(); // 用于标识策略类型
}
/**
* 邮件发送策略
*/
@Component
public class EmailSendStrategy implements MessageSendStrategy {
@Override
public void send(String message, String recipient) {
System.out.println("发送邮件到: " + recipient);
System.out.println("内容: " + message);
// 实际邮件发送逻辑
}
@Override
public String getType() {
return "email";
}
}
/**
* 短信发送策略
*/
@Component
public class SmsSendStrategy implements MessageSendStrategy {
@Override
public void send(String message, String recipient) {
System.out.println("发送短信到: " + recipient);
System.out.println("内容: " + message);
// 实际短信发送逻辑
}
@Override
public String getType() {
return "sms";
}
}
/**
* 推送通知策略
*/
@Component
public class PushNotificationStrategy implements MessageSendStrategy {
@Override
public void send(String message, String recipient) {
System.out.println("发送推送到: " + recipient);
System.out.println("内容: " + message);
// 实际推送逻辑
}
@Override
public String getType() {
return "push";
}
}
/**
* 消息服务(上下文)
*/
@Service
public class MessageService {
private final Map<String, MessageSendStrategy> strategies;
@Autowired
public MessageService(List<MessageSendStrategy> strategyList) {
// Spring自动注入所有实现了MessageSendStrategy的Bean
this.strategies = strategyList.stream()
.collect(Collectors.toMap(
MessageSendStrategy::getType,
strategy -> strategy
));
}
public void sendMessage(String type, String message, String recipient) {
MessageSendStrategy strategy = strategies.get(type);
if (strategy == null) {
throw new IllegalArgumentException("不支持的消息类型: " + type);
}
strategy.send(message, recipient);
}
}
/**
* 控制器
*/
@RestController
@RequestMapping("/api/message")
public class MessageController {
@Autowired
private MessageService messageService;
@PostMapping("/send")
public ResponseEntity<String> sendMessage(
@RequestParam String type,
@RequestParam String message,
@RequestParam String recipient
) {
try {
messageService.sendMessage(type, message, recipient);
return ResponseEntity.ok("消息发送成功");
} catch (Exception e) {
return ResponseEntity.badRequest().body("发送失败: " + e.getMessage());
}
}
}
十、最佳实践与注意事项
10.1 最佳实践
1. 使用枚举管理策略类型
public enum DiscountType {
NONE(new NormalPriceStrategy()),
VIP(new VipPriceStrategy()),
SUPER_VIP(new SuperVipPriceStrategy());
private final PriceStrategy strategy;
DiscountType(PriceStrategy strategy) {
this.strategy = strategy;
}
public PriceStrategy getStrategy() {
return strategy;
}
}
// 使用
PriceStrategy strategy = DiscountType.VIP.getStrategy();
2. 策略接口提供默认实现(Java 8+)
public interface PaymentStrategy {
boolean pay(double amount);
// 默认实现
default String getPaymentName() {
return this.getClass().getSimpleName();
}
default void beforePay() {
System.out.println("准备支付...");
}
default void afterPay() {
System.out.println("支付完成!");
}
}
3. 使用函数式接口简化策略(Java 8+)
对于简单的策略,可以直接使用Lambda表达式:
@FunctionalInterface
public interface DiscountCalculator {
double calculate(double price);
}
// 使用Lambda定义策略
DiscountCalculator noDiscount = price -> price;
DiscountCalculator tenPercent = price -> price * 0.9;
DiscountCalculator twentyPercent = price -> price * 0.8;
// 使用方法引用
DiscountCalculator custom = MyClass::calculateDiscount;
4. 策略缓存和复用
public class StrategyCache {
private static final Map<String, PaymentStrategy> cache = new ConcurrentHashMap<>();
public static PaymentStrategy getStrategy(String type) {
return cache.computeIfAbsent(type, k -> createStrategy(k));
}
private static PaymentStrategy createStrategy(String type) {
// 创建策略对象
switch (type) {
case "alipay": return new AlipayStrategy();
case "wechat": return new WechatPayStrategy();
default: throw new IllegalArgumentException("Unknown type: " + type);
}
}
}
10.2 注意事项
1. 避免策略类爆炸
当策略类数量过多时,考虑:
- 使用配置文件或数据库存储策略参数
- 合并相似的策略
- 使用模板方法模式提取公共逻辑
2. 策略选择逻辑不要过于复杂
如果策略选择逻辑很复杂,可以:
- 使用策略选择器(Strategy Selector)
- 使用责任链模式辅助选择
- 使用规则引擎
public class StrategySelector {
public static PriceStrategy selectStrategy(User user, Order order) {
if (user.isSuperVip()) {
return new SuperVipPriceStrategy();
} else if (user.isVip()) {
return new VipPriceStrategy();
} else if (order.getTotalAmount() > 1000) {
return new PromotionPriceStrategy(1000, 100);
}
return new NormalPriceStrategy();
}
}
3. 线程安全问题
如果策略对象包含可变状态,需要考虑线程安全:
// 不安全的策略(包含可变状态)
public class UnsafeStrategy implements Strategy {
private int count = 0; // 可变状态
@Override
public void execute() {
count++; // 线程不安全
}
}
// 安全的策略(无状态或使用ThreadLocal)
public class SafeStrategy implements Strategy {
@Override
public void execute(Context context) {
int count = context.getCount();
context.setCount(count + 1);
}
}
4. 策略的生命周期管理
在Spring等容器中,注意策略Bean的作用域:
@Component
@Scope("prototype") // 每次获取都创建新实例
public class StatefulStrategy implements PaymentStrategy {
private String transactionId;
// 包含状态的策略使用prototype作用域
}
@Component
@Scope("singleton") // 单例(默认)
public class StatelessStrategy implements PaymentStrategy {
// 无状态的策略可以使用singleton作用域
}
十一、总结
策略模式是一种非常实用的设计模式,它通过封装算法、面向接口编程和运行时切换等特性,帮助我们编写出更加灵活、可维护的代码。