1. 策略模式与工厂模式的黄金组合
在面向对象编程中,设计模式就像老厨师手中的秘制配方,能让代码烹饪出更美味的架构。最近我在重构一个电商促销系统时,发现策略模式+工厂模式的组合拳特别适合处理复杂的业务规则变化。这种搭配既能保持代码的扩展性,又能简化客户端调用,就像给代码装上了自动挡变速箱。
想象一下这样的场景:你的系统需要支持满减、折扣、赠品等多种促销策略,而且未来还会不断增加新玩法。如果直接用if-else堆砌,代码很快就会变成难以维护的意大利面条。而策略模式将每种算法封装成独立类,工厂模式则负责隐藏对象创建细节,二者结合产生的化学反应会让系统具备惊人的弹性。
2. 模式原理深度解析
2.1 策略模式的精髓
策略模式的核心在于定义算法族,将每个算法封装起来,使它们可以互相替换。这种模式让算法的变化独立于使用算法的客户端,就像游戏手柄上的ABXY键,每个按键对应不同技能,但操作方式完全一致。
在Java中实现策略模式通常包含三个角色:
- 策略接口(Strategy):定义所有支持的算法接口
- 具体策略(ConcreteStrategy):实现策略接口的具体算法类
- 上下文(Context):持有一个策略引用,提供接口给客户端调用
// 策略接口 public interface PromotionStrategy { BigDecimal apply(BigDecimal price); } // 具体策略实现 public class DiscountStrategy implements PromotionStrategy { private final BigDecimal rate; public DiscountStrategy(BigDecimal rate) { this.rate = rate; } @Override public BigDecimal apply(BigDecimal price) { return price.multiply(rate); } }2.2 工厂模式的妙用
简单工厂模式虽然不在GoF 23种设计模式之列,但在实际开发中非常实用。它就像个智能快递柜,客户端只需要告诉工厂"我要取促销策略A",工厂就会返回对应的策略实例,完全不用关心这个策略是怎么创建的。
工厂模式主要解决以下问题:
- 对象创建逻辑集中管理
- 客户端与具体实现解耦
- 便于统一修改创建逻辑
public class StrategyFactory { private static final Map<String, PromotionStrategy> STRATEGIES = new HashMap<>(); static { STRATEGIES.put("DISCOUNT", new DiscountStrategy(new BigDecimal("0.8"))); STRATEGIES.put("FULL_REDUCTION", new FullReductionStrategy(100, 20)); } public static PromotionStrategy getStrategy(String type) { return STRATEGIES.get(type); } }3. 实战:电商促销系统改造
3.1 原始代码的问题诊断
在改造前的促销系统中,订单服务直接包含了所有促销逻辑:
public class OrderService { public BigDecimal calculatePrice(String promoType, BigDecimal price) { if ("DISCOUNT".equals(promoType)) { return price.multiply(new BigDecimal("0.8")); } else if ("FULL_REDUCTION".equals(promoType)) { if (price.compareTo(new BigDecimal(100)) >= 0) { return price.subtract(new BigDecimal(20)); } } // 更多if-else... return price; } }这段代码存在明显问题:
- 违反开闭原则:新增策略需要修改现有代码
- 方法职责过重:包含所有促销逻辑
- 难以单元测试:各种条件分支交织在一起
3.2 重构后的优雅实现
首先定义策略接口和具体实现:
public interface PromotionStrategy { String getType(); BigDecimal apply(BigDecimal price); } public class DiscountStrategy implements PromotionStrategy { private final BigDecimal rate; public DiscountStrategy(BigDecimal rate) { this.rate = rate; } @Override public String getType() { return "DISCOUNT"; } @Override public BigDecimal apply(BigDecimal price) { return price.multiply(rate); } }然后实现策略工厂,这里使用枚举+Map优化:
public class StrategyFactory { private static final Map<String, PromotionStrategy> STRATEGY_MAP = new HashMap<>(); static { registerStrategy(new DiscountStrategy(new BigDecimal("0.8"))); registerStrategy(new FullReductionStrategy(100, 20)); } private static void registerStrategy(PromotionStrategy strategy) { STRATEGY_MAP.put(strategy.getType(), strategy); } public static PromotionStrategy getStrategy(String type) { PromotionStrategy strategy = STRATEGY_MAP.get(type); if (strategy == null) { throw new IllegalArgumentException("Unknown strategy type: " + type); } return strategy; } }最终订单服务变得极其简洁:
public class OrderService { public BigDecimal calculatePrice(String promoType, BigDecimal price) { PromotionStrategy strategy = StrategyFactory.getStrategy(promoType); return strategy.apply(price); } }4. 进阶优化技巧
4.1 使用Spring优化工厂实现
在Spring项目中,我们可以利用依赖注入和Bean特性实现更优雅的工厂:
@Component public class StrategyFactory { private final Map<String, PromotionStrategy> strategyMap; @Autowired public StrategyFactory(List<PromotionStrategy> strategies) { strategyMap = strategies.stream() .collect(Collectors.toMap( PromotionStrategy::getType, Function.identity() )); } public PromotionStrategy getStrategy(String type) { return Optional.ofNullable(strategyMap.get(type)) .orElseThrow(() -> new IllegalArgumentException("Unknown strategy type: " + type)); } }4.2 策略模式的线程安全
策略对象通常是无状态的,可以安全地在多线程环境下共享。但如果策略需要维护状态,则需要考虑线程安全问题:
public class CounterStrategy implements PromotionStrategy { private final AtomicInteger counter = new AtomicInteger(0); @Override public BigDecimal apply(BigDecimal price) { counter.incrementAndGet(); return price.subtract(BigDecimal.valueOf(counter.get())); } }4.3 组合策略的实现
有时需要组合多个策略,可以通过装饰器模式实现:
public class CompositeStrategy implements PromotionStrategy { private final List<PromotionStrategy> strategies; public CompositeStrategy(PromotionStrategy... strategies) { this.strategies = Arrays.asList(strategies); } @Override public BigDecimal apply(BigDecimal price) { BigDecimal result = price; for (PromotionStrategy strategy : strategies) { result = strategy.apply(result); } return result; } }5. 避坑指南与最佳实践
5.1 常见问题排查
策略未生效:
- 检查工厂注册的策略类型是否与传入类型完全匹配(注意大小写)
- 确保没有遗漏策略类的@Component注解(Spring环境)
- 在工厂初始化后打印所有注册的策略类型进行验证
NPE异常:
- 工厂方法应对未知策略类型返回默认策略或抛出明确异常
- 使用Optional包装返回值可以更安全
性能问题:
- 避免在策略实现中创建大量临时对象
- 考虑使用策略对象池(对象创建成本高时)
5.2 设计注意事项
策略粒度控制:
- 策略划分过粗会导致单个策略过于复杂
- 策略划分过细会增加系统复杂度
- 建议按业务维度划分,保持单一职责
工厂管理范围:
- 不要将所有策略都放在一个工厂中
- 可以按业务域划分多个工厂类
- 或者使用分层工厂结构
与其它模式配合:
- 结合模板方法模式处理策略中的公共逻辑
- 使用装饰器模式实现策略组合
- 通过责任链模式实现策略的链式调用
5.3 测试建议
单元测试策略:
- 为每个策略实现编写独立的测试用例
- 测试边界条件和异常情况
工厂测试要点:
- 验证工厂返回的策略实例类型正确
- 测试传入无效类型时的异常处理
集成测试场景:
- 模拟真实调用链验证策略效果
- 测试策略组合使用的正确性
@Test void testDiscountStrategy() { PromotionStrategy strategy = new DiscountStrategy(new BigDecimal("0.8")); assertEquals(new BigDecimal("80.00"), strategy.apply(new BigDecimal("100.00"))); } @Test void testStrategyFactory() { PromotionStrategy strategy = StrategyFactory.getStrategy("DISCOUNT"); assertTrue(strategy instanceof DiscountStrategy); assertThrows(IllegalArgumentException.class, () -> { StrategyFactory.getStrategy("UNKNOWN"); }); }6. 模式应用场景扩展
6.1 支付网关集成
支付渠道(支付宝、微信、银联)的选择是策略模式的经典应用场景:
public interface PaymentStrategy { PaymentResult pay(PaymentRequest request); } @Service public class AlipayStrategy implements PaymentStrategy { @Override public PaymentResult pay(PaymentRequest request) { // 调用支付宝SDK } } @Service public class WechatPayStrategy implements PaymentStrategy { @Override public PaymentResult pay(PaymentRequest request) { // 调用微信支付API } }6.2 报表生成引擎
不同格式的报表生成(PDF、Excel、HTML)也适合使用策略模式:
public interface ReportGenerator { byte[] generate(ReportData data); } @Component public class PdfReportGenerator implements ReportGenerator { @Override public byte[] generate(ReportData data) { // 使用iText等库生成PDF } }6.3 运费计算系统
电商中的运费计算规则经常变化,策略模式可以轻松应对:
public interface ShippingCalculator { BigDecimal calculate(Order order); } @Component public class FreeShippingCalculator implements ShippingCalculator { @Override public BigDecimal calculate(Order order) { return order.getTotal().compareTo(BigDecimal.valueOf(199)) >= 0 ? BigDecimal.ZERO : BigDecimal.TEN; } }在实际项目中,策略+工厂的组合就像瑞士军刀,特别适合处理以下场景:
- 业务规则需要频繁变更或扩展
- 系统中存在多重条件分支
- 需要动态选择算法实现
- 希望隔离算法实现和使用代码
这种模式组合的威力在于,当新增一种策略时,你只需要新增一个策略类并在工厂中注册,完全不用修改现有代码。就像乐高积木一样,系统的扩展变得轻松而优雅。