一、前言
作为一名在Java世界里摸爬滚打了十年的“老码农”,我见证过无数项目的兴起与重构。其中一个最常见的、也是最让人头疼的问题,就是随着业务爆炸式增长,代码中充斥着庞大而脆弱的 if-else 或 switch-case 语句。
它们像代码的 “肿瘤”,初始时良性,但随着时间推移,变得难以维护、难以测试、难以扩展。
今天,我们就来深入探讨一下解决这个痛点的利器——策略模式。这不仅仅是教科书上的一个设计模式,更是我们日常开发中,应对业务多变性的核心架构手段。
一个真实的场景:从“屎山”代码说起
- 假设我们正在开发一个电商平台的支付模块。最初,系统只支持支付宝支付。代码可能是这样的:
1 2 3 4 5 6 7 8 9 10
| public class PaymentService { public void pay(String orderId, String paymentType) { if ("alipay".equals(paymentType)) { System.out.println("使用支付宝支付订单:" + orderId); } } }
|
- 业务发展迅猛,很快就要接入微信支付。你可能会这样修改:
1 2 3 4 5 6 7 8 9 10 11 12
| public class PaymentService { public void pay(String orderId, String paymentType) { if ("alipay".equals(paymentType)) { System.out.println("使用支付宝支付订单:" + orderId); } else if ("wechat_pay".equals(paymentType)) { System.out.println("使用微信支付订单:" + orderId); } } }
|
紧接着,银联支付、Apple Pay、信用卡支付、会员积分抵扣等需求接踵而至。这个 pay 方法迅速膨胀成数百行的”巨无霸”,存在以下严重问题:
- 违反开闭原则:每次新增支付方式都要修改核心类
- 单一职责沦陷:一个方法承担过多职责
- 测试复杂度高:条件分支组合爆炸,难以覆盖所有场景
- 代码腐化:随着时间推移,逐渐演变为”屎山”代码
策略模式正是拯救这种架构腐化的银弹,它帮助我们实现关注点分离,构建可扩展的优雅架构。
二、策略模式的核心思想与架构
1. 官方定义
策略模式(Strategy Pattern)定义了一系列的算法,并将每一个算法封装起来,使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户。
2. 通俗理解
将策略模式想象成高级工具箱:
- 支付方式(支付宝、微信等)是不同的”专业工具”
PaymentService(使用者)不需要关心具体工具的内部机制
- 需要更换工具?直接从工具箱选取即可,无需改造使用者的”手”
3. 模式结构
策略模式通常包含三个核心角色:
- 环境类: 持有一个策略类的引用,最终给客户端调用。对应我们的
PaymentService。
- 抽象策略: 定义了所有具体策略需要实现的接口或抽象类。它规定了策略家族的行为规范。
- 具体策略: 实现了抽象策略接口,提供了具体的算法实现。对应
AlipayStrategy, WechatPayStrategy 等。
1 2 3 4 5 6
| ┌─────────────────┐ ┌─────────────────────┐ ┌───────────────────┐ │ 环境类 │ │ 抽象策略 │ │ 具体策略 │ │ (Context) │ ───────> │ (Strategy) │ <─────── │ (Concrete │ │ PaymentService│ │ PaymentStrategy │ │ Strategy) │ │ │ │ │ │ AlipayStrategy │ └─────────────────┘ └─────────────────────┘ └───────────────────┘
|
下面,我们就用代码来重构上面的支付场景。
三、实战重构:构建高扩展的支付架构
步骤1:定义抽象策略(策略接口)
这是架构的基石,定义了所有支付策略必须遵守的”协议”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
public interface PaymentStrategy {
String getPaymentType();
PayResult pay(String orderId, BigDecimal amount);
default RefundResult refund(String orderId, BigDecimal amount) { throw new UnsupportedOperationException("该支付方式不支持退款"); } }
|
这里我定义了一个 getPaymentType() 方法,用于后续根据类型查找策略,还定义了一个统一的 pay 方法。PayResult 是一个简单的支付结果封装类。
步骤2:实现具体策略(算法具体化)
现在,我们将每种支付方式独立成一个类,实现 PaymentStrategy 接口。
1、支付宝策略:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
|
@Component @Slf4j public class AlipayStrategy implements PaymentStrategy { private final AlipayClient alipayClient; public AlipayStrategy(AlipayClient alipayClient) { this.alipayClient = alipayClient; }
@Override public String getPaymentType() { return "alipay"; }
@Override public PayResult pay(String orderId, BigDecimal amount) { try { log.info("发起支付宝支付,订单:{},金额:{}", orderId, amount); AlipayTradePayRequest request = buildAlipayRequest(orderId, amount); AlipayTradePayResponse response = alipayClient.execute(request); if ("10000".equals(response.getCode())) { log.info("支付宝支付成功,订单:{}", orderId); return new PayResult(200, "支付宝支付成功", orderId); } else { log.error("支付宝支付失败,订单:{},原因:{}", orderId, response.getMsg()); return new PayResult(500, "支付宝支付失败:" + response.getMsg(), orderId); } } catch (Exception e) { log.error("支付宝支付异常,订单:{}", orderId, e); return new PayResult(500, "支付宝支付异常:" + e.getMessage(), orderId); } } @Override public RefundResult refund(String orderId, BigDecimal amount) { return new RefundResult(200, "支付宝退款成功", orderId, amount); } private AlipayTradePayRequest buildAlipayRequest(String orderId, BigDecimal amount) { return new AlipayTradePayRequest(); } }
|
2、微信支付策略:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
|
@Component @Slf4j public class WechatPayStrategy implements PaymentStrategy { private final WxPayService wxPayService;
public WechatPayStrategy(WxPayService wxPayService) { this.wxPayService = wxPayService; }
@Override public String getPaymentType() { return "wechat_pay"; }
@Override public PayResult pay(String orderId, BigDecimal amount) { try { log.info("发起微信支付,订单:{},金额:{}", orderId, amount); WxPayUnifiedOrderRequest request = buildWechatRequest(orderId, amount); WxPayUnifiedOrderResult result = wxPayService.unifiedOrder(request); if ("SUCCESS".equals(result.getReturnCode())) { log.info("微信支付成功,订单:{}", orderId); return new PayResult(200, "微信支付成功", orderId); } else { log.error("微信支付失败,订单:{},原因:{}", orderId, result.getReturnMsg()); return new PayResult(500, "微信支付失败:" + result.getReturnMsg(), orderId); } } catch (Exception e) { log.error("微信支付异常,订单:{}", orderId, e); return new PayResult(500, "微信支付异常:" + e.getMessage(), orderId); } } private WxPayUnifiedOrderRequest buildWechatRequest(String orderId, BigDecimal amount) { return new WxPayUnifiedOrderRequest(); } }
|
步骤3:构建策略工厂(统一管理)
策略工厂是策略模式的核心协调者,负责策略的注册、管理和获取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
@Component public class PaymentStrategyFactory {
private final Map<String, PaymentStrategy> strategyMap = new ConcurrentHashMap<>();
public PaymentStrategyFactory(List<PaymentStrategy> strategies) { for (PaymentStrategy strategy : strategies) { strategyMap.put(strategy.getPaymentType(), strategy); } }
public PaymentStrategy getStrategy(String paymentType) { PaymentStrategy strategy = strategyMap.get(paymentType); if (strategy == null) { throw new RuntimeException("不支持的支付类型:" + paymentType); } return strategy; } }
|
这里利用了Spring的依赖注入特性,在工厂初始化时,自动将所有 PaymentStrategy 类型的Bean收集到一个Map中,Key就是我们定义的 getPaymentType() 返回值。
对于初始化成本高的策略,也可以实现懒加载机制:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Component public class LazyInitPaymentStrategyFactory { private final Map<String, PaymentStrategy> strategyMap = new ConcurrentHashMap<>(); private final ApplicationContext applicationContext; public PaymentStrategy getStrategy(String paymentType) { return strategyMap.computeIfAbsent(paymentType, this::createStrategy); } private PaymentStrategy createStrategy(String paymentType) { return applicationContext.getBeansOfType(PaymentStrategy.class) .values().stream() .filter(s -> s.getPaymentType().equals(paymentType)) .findFirst() .orElseThrow(() -> new RuntimeException("不支持的支付类型:" + paymentType)); } }
|
步骤4:改造环境类(PaymentService)
现在,我们的 PaymentService 变得极其清爽和稳固。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
@Service public class PaymentService {
private final PaymentStrategyFactory strategyFactory;
public PaymentService(PaymentStrategyFactory strategyFactory) { this.strategyFactory = strategyFactory; }
public PayResult executePay(String orderId, String paymentType, BigDecimal amount) { PaymentStrategy strategy = strategyFactory.getStrategy(paymentType); PayResult result = strategy.pay(orderId, amount); System.out.println("支付完成,结果:" + result.getMessage()); return result; } }
|
PaymentService 支付方法通过工厂获取对应的策略进行支付,后期扩展支付类型,这块的代码完全不需要修改,只需要添加对应的策略实现类即可。
四、策略模式的威力与优势分析
经过这番重构,我们的代码发生了质的变化:
符合开闭原则: 当需要新增一种支付方式(例如 BankCardStrategy)时,我们只需要新建一个类实现 PaymentStrategy 接口,并将其注册为Spring Bean即可。完全不需要修改 PaymentService、PaymentStrategyFactory 或任何其他已有的策略类。系统的扩展性极强。
消除复杂条件判断: PaymentService 中的 if-else 彻底消失,代码可读性和可维护性大幅提升。
算法独立化,易于理解和测试: 每种支付逻辑都被封闭在各自的策略类中,职责单一。我们可以非常轻松地对每个策略进行单元测试。
1 2 3 4 5 6
| @Test public void testAlipayStrategy() { AlipayStrategy strategy = new AlipayStrategy(); PayResult result = strategy.pay("123", new BigDecimal("100.00")); assertEquals(200, result.getCode()); }
|
运行时动态切换策略: 客户端(如Controller)通过传入不同的 paymentType,就可以在运行时灵活地切换支付算法。
五、总结
策略模式的应用场景远不止支付,任何存在多种算法、业务规则或系统行为需要动态选择的场景,都是它的用武之地:
- 营销优惠系统: 满减、折扣、赠品、优惠券等不同的优惠计算规则。
- 数据导出系统: 导出为Excel、PDF、CSV等不同格式。
- 消息通知系统: 通过短信、邮件、App推送、微信模板消息等不同渠道发送通知。
- 排序算法: 根据不同条件(价格、销量、评分)使用不同的排序策略。
- 权限校验: 用户名密码、短信验证码、第三方OAuth等多种登录方式。
回顾十年开发历程,我深刻体会到,设计模式并非炫技的“花拳绣腿”,而是应对软件复杂性的务实工具。策略模式教会我们的,是一种 “分而治之” 和 “面向接口编程” 的架构思想。
当你的代码中出现以下“坏味道”时,请毫不犹豫地考虑策略模式:
- 一个方法内部存在大量的条件判断分支。
- 同一个算法在多个地方被复制粘贴。
- 经常因为新增一种业务类型而修改核心业务类。
从满屏 if-else 的“屎山”代码,到结构清晰、扩展性强的优雅架构,策略模式为我们提供了一条清晰的演进路径。掌握它,不仅能让你写出更健壮的代码,更能让你在应对产品经理天马行空的需求时,多一份从容与自信。
记住,优秀的代码不是一次写成的,而是在不断的重构与思考中演化而来的。