策略模式
策略模式定义一系列算法,把它们封装起来,并且使它们可互相替换。策略模式让算法独立于使用它的客户而变化。
📋 模式概述
定义: 定义一系列算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。
适用场景:
- 系统中有许多类,它们的区别仅在于行为不同
- 需要动态地选择算法
- 避免使用大量的if-else或switch-case语句
- 算法的实现和使用分离
🎯 一、项目案例:优惠券计算策略
1.1 问题场景
在电商系统中,有多种优惠券类型(满减券、折扣券、直减券等),每种优惠券的计算逻辑不同。如果使用if-else处理,代码会非常臃肿。
问题代码示例:
java
/**
* 订单服务 - 使用if-else计算优惠(问题示例)
*/
public class OrderService {
public BigDecimal calculateDiscount(Order order, Coupon coupon) {
BigDecimal discount = BigDecimal.ZERO;
if ("FULL_REDUCTION".equals(coupon.getType())) {
// 满减券逻辑
if (order.getTotalAmount().compareTo(coupon.getMinAmount()) >= 0) {
discount = coupon.getDiscountAmount();
}
} else if ("DISCOUNT".equals(coupon.getType())) {
// 折扣券逻辑
discount = order.getTotalAmount()
.multiply(coupon.getDiscountRate())
.setScale(2, RoundingMode.HALF_UP);
} else if ("DIRECT_REDUCTION".equals(coupon.getType())) {
// 直减券逻辑
discount = coupon.getDiscountAmount();
} else if ("PERCENTAGE".equals(coupon.getType())) {
// 百分比折扣逻辑
discount = order.getTotalAmount()
.multiply(coupon.getDiscountPercent())
.divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
}
// ... 更多if-else
return discount;
}
}问题:
- 代码臃肿,可读性差
- 新增优惠券类型需要修改现有代码
- 违反开闭原则
- 难以测试和维护
1.2 策略模式解决方案
项目背景: 电商系统优惠券计算
步骤1:定义策略接口
java
/**
* 优惠券计算策略接口
* 项目案例:电商系统优惠券计算
*/
public interface DiscountStrategy {
/**
* 计算优惠金额
* @param order 订单
* @param coupon 优惠券
* @return 优惠金额
*/
BigDecimal calculateDiscount(Order order, Coupon coupon);
/**
* 获取策略类型
* @return 策略类型
*/
String getType();
}步骤2:实现具体策略
java
/**
* 满减券策略
*/
@Component
public class FullReductionStrategy implements DiscountStrategy {
@Override
public BigDecimal calculateDiscount(Order order, Coupon coupon) {
// 满减券:订单金额达到最低金额时,减免固定金额
BigDecimal totalAmount = order.getTotalAmount();
BigDecimal minAmount = coupon.getMinAmount();
BigDecimal discountAmount = coupon.getDiscountAmount();
if (totalAmount.compareTo(minAmount) >= 0) {
// 优惠金额不能超过订单金额
return discountAmount.min(totalAmount);
}
return BigDecimal.ZERO;
}
@Override
public String getType() {
return "FULL_REDUCTION";
}
}java
/**
* 折扣券策略
*/
@Component
public class DiscountStrategy implements DiscountStrategy {
@Override
public BigDecimal calculateDiscount(Order order, Coupon coupon) {
// 折扣券:按折扣率计算优惠金额
BigDecimal totalAmount = order.getTotalAmount();
BigDecimal discountRate = coupon.getDiscountRate();
// 折扣率应该在0-1之间
if (discountRate.compareTo(BigDecimal.ZERO) > 0
&& discountRate.compareTo(BigDecimal.ONE) <= 0) {
return totalAmount
.multiply(BigDecimal.ONE.subtract(discountRate))
.setScale(2, RoundingMode.HALF_UP);
}
return BigDecimal.ZERO;
}
@Override
public String getType() {
return "DISCOUNT";
}
}java
/**
* 直减券策略
*/
@Component
public class DirectReductionStrategy implements DiscountStrategy {
@Override
public BigDecimal calculateDiscount(Order order, Coupon coupon) {
// 直减券:直接减免固定金额
BigDecimal totalAmount = order.getTotalAmount();
BigDecimal discountAmount = coupon.getDiscountAmount();
// 优惠金额不能超过订单金额
return discountAmount.min(totalAmount);
}
@Override
public String getType() {
return "DIRECT_REDUCTION";
}
}java
/**
* 百分比折扣策略
*/
@Component
public class PercentageDiscountStrategy implements DiscountStrategy {
@Override
public BigDecimal calculateDiscount(Order order, Coupon coupon) {
// 百分比折扣:按百分比计算优惠金额
BigDecimal totalAmount = order.getTotalAmount();
BigDecimal discountPercent = coupon.getDiscountPercent();
// 百分比应该在0-100之间
if (discountPercent.compareTo(BigDecimal.ZERO) > 0
&& discountPercent.compareTo(new BigDecimal("100")) <= 0) {
return totalAmount
.multiply(discountPercent)
.divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
}
return BigDecimal.ZERO;
}
@Override
public String getType() {
return "PERCENTAGE";
}
}步骤3:创建策略上下文
java
/**
* 优惠券计算上下文
* 项目案例:电商系统优惠券计算
*/
@Service
public class DiscountContext {
// 存储所有策略
private final Map<String, DiscountStrategy> strategies = new HashMap<>();
/**
* 通过构造函数注入所有策略
*/
public DiscountContext(List<DiscountStrategy> strategies) {
// 将策略注册到Map中
for (DiscountStrategy strategy : strategies) {
this.strategies.put(strategy.getType(), strategy);
}
}
/**
* 计算优惠金额
*/
public BigDecimal calculateDiscount(Order order, Coupon coupon) {
DiscountStrategy strategy = strategies.get(coupon.getType());
if (strategy == null) {
throw new IllegalArgumentException("不支持的优惠券类型: " + coupon.getType());
}
return strategy.calculateDiscount(order, coupon);
}
}步骤4:使用策略模式
java
/**
* 订单服务 - 使用策略模式计算优惠
* 项目案例:电商订单优惠计算
*/
@Service
public class OrderService {
@Autowired
private DiscountContext discountContext;
/**
* 计算订单最终金额
*/
public BigDecimal calculateFinalAmount(Order order, Coupon coupon) {
BigDecimal totalAmount = order.getTotalAmount();
// 使用策略模式计算优惠
BigDecimal discount = discountContext.calculateDiscount(order, coupon);
// 最终金额 = 原价 - 优惠金额
BigDecimal finalAmount = totalAmount.subtract(discount);
// 最终金额不能小于0
return finalAmount.max(BigDecimal.ZERO);
}
/**
* 创建订单并应用优惠券
*/
public Order createOrderWithCoupon(CreateOrderRequest request) {
// 创建订单
Order order = new Order();
order.setItems(request.getItems());
order.setTotalAmount(calculateTotalAmount(request.getItems()));
// 如果有优惠券,计算优惠
if (request.getCouponId() != null) {
Coupon coupon = couponRepository.findById(request.getCouponId());
BigDecimal discount = discountContext.calculateDiscount(order, coupon);
order.setDiscountAmount(discount);
order.setFinalAmount(order.getTotalAmount().subtract(discount));
order.setCouponId(coupon.getId());
} else {
order.setFinalAmount(order.getTotalAmount());
}
// 保存订单
return orderRepository.save(order);
}
private BigDecimal calculateTotalAmount(List<OrderItem> items) {
return items.stream()
.map(item -> item.getPrice().multiply(new BigDecimal(item.getQuantity())))
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
}好处:
- 消除if-else: 用策略替换大量的条件判断
- 易于扩展: 新增优惠券类型只需添加新的策略类
- 易于测试: 每个策略可以独立测试
- 符合开闭原则: 对扩展开放,对修改关闭
🎯 二、项目案例:数据导出策略
2.1 问题场景
在报表系统中,需要支持多种数据导出格式(Excel、PDF、CSV等),每种格式的导出逻辑不同。
2.2 策略模式解决方案
项目背景: 报表系统数据导出
java
/**
* 数据导出策略接口
*/
public interface ExportStrategy {
/**
* 导出数据
* @param data 数据
* @param fileName 文件名
* @return 文件路径
*/
String export(List<?> data, String fileName);
/**
* 获取支持的格式
*/
String getFormat();
}java
/**
* Excel导出策略
*/
@Component
public class ExcelExportStrategy implements ExportStrategy {
@Override
public String export(List<?> data, String fileName) {
try (Workbook workbook = new XSSFWorkbook();
FileOutputStream fos = new FileOutputStream(fileName + ".xlsx")) {
Sheet sheet = workbook.createSheet("数据");
// 创建表头
Row headerRow = sheet.createRow(0);
// ... 设置表头
// 填充数据
int rowNum = 1;
for (Object item : data) {
Row row = sheet.createRow(rowNum++);
// ... 填充行数据
}
workbook.write(fos);
return fileName + ".xlsx";
} catch (Exception e) {
throw new RuntimeException("Excel导出失败", e);
}
}
@Override
public String getFormat() {
return "EXCEL";
}
}java
/**
* PDF导出策略
*/
@Component
public class PDFExportStrategy implements ExportStrategy {
@Override
public String export(List<?> data, String fileName) {
try {
Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document,
new FileOutputStream(fileName + ".pdf"));
document.open();
// 创建表格
PdfPTable table = new PdfPTable(getColumnCount());
// ... 填充表格数据
document.add(table);
document.close();
return fileName + ".pdf";
} catch (Exception e) {
throw new RuntimeException("PDF导出失败", e);
}
}
@Override
public String getFormat() {
return "PDF";
}
private int getColumnCount() {
// 返回列数
return 5;
}
}java
/**
* CSV导出策略
*/
@Component
public class CSVExportStrategy implements ExportStrategy {
@Override
public String export(List<?> data, String fileName) {
try (FileWriter writer = new FileWriter(fileName + ".csv");
CSVWriter csvWriter = new CSVWriter(writer)) {
// 写入表头
csvWriter.writeNext(getHeaders());
// 写入数据
for (Object item : data) {
csvWriter.writeNext(convertToRow(item));
}
return fileName + ".csv";
} catch (Exception e) {
throw new RuntimeException("CSV导出失败", e);
}
}
@Override
public String getFormat() {
return "CSV";
}
private String[] getHeaders() {
return new String[]{"列1", "列2", "列3"};
}
private String[] convertToRow(Object item) {
// 转换为CSV行
return new String[]{"值1", "值2", "值3"};
}
}java
/**
* 导出服务 - 使用策略模式
*/
@Service
public class ExportService {
private final Map<String, ExportStrategy> strategies = new HashMap<>();
public ExportService(List<ExportStrategy> strategies) {
for (ExportStrategy strategy : strategies) {
this.strategies.put(strategy.getFormat(), strategy);
}
}
/**
* 导出数据
*/
public String exportData(List<?> data, String format, String fileName) {
ExportStrategy strategy = strategies.get(format.toUpperCase());
if (strategy == null) {
throw new IllegalArgumentException("不支持的导出格式: " + format);
}
return strategy.export(data, fileName);
}
}📖 策略模式最佳实践
1. 策略的选择
- 使用工厂模式选择策略
- 使用Spring的依赖注入管理策略
- 根据配置动态选择策略
2. 策略的注册
java
/**
* 策略管理器 - 自动注册策略
*/
@Component
public class StrategyManager {
private final Map<String, DiscountStrategy> strategies = new ConcurrentHashMap<>();
@Autowired
public StrategyManager(List<DiscountStrategy> strategies) {
for (DiscountStrategy strategy : strategies) {
register(strategy.getType(), strategy);
}
}
public void register(String type, DiscountStrategy strategy) {
strategies.put(type, strategy);
}
public DiscountStrategy getStrategy(String type) {
return strategies.get(type);
}
}✅ 学习检查清单
- [ ] 已理解策略模式的定义和适用场景
- [ ] 已掌握如何用策略模式消除if-else
- [ ] 已在项目中应用策略模式
- [ ] 已了解策略模式和工厂模式的结合使用
- [ ] 已理解策略模式的优缺点