大家好,我是星星。
今天我们一起来探讨一下,策略模式是如何使用的,众所周知啊,策略模式可以说是使用的相当频繁的,许多的业务场景下都能用到策略模式,那策略模式到底是什么,究竟应该如何使用呢?今天我们将用一个简单的小案例来说明这种设计模式是如何使用的。

1.策略模式的核心思想

策略模式在 GoF 的《设计模式》一书中,是这样定义的:

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

定义一组算法类,将每个算法分别封装起来,让它们可以互相替换。策略模式使这些算法在客户端调用它们的时候能够互不影响地变化,客户端代指使用算法的代码。

核心组件:

  1. 策略接口:定义了所有具体策略必须实现的方法。
  2. 具体策略类:实现了策略接口,提供了具体的算法或行为。
  3. 上下文:持有一个策略接口的引用,通过组合的方式使用策略。上下文本身不决定使用哪个策略,而是由客户端来决定。

核心原则:组合优于继承,面向接口编程。

2.为什么策略模式在分布式项目中特别重要呢?

在单体应用中,策略模式主要用来解耦算法。但在分布式、微服务架构下,它的价值被放大了:
1.分布式系统通常服务多个渠道、多个客户(不同商户、不同地区),每个渠道或客户的业务逻辑可能有细微差别。策略模式就是专门用来避免代码全部写成“if-else”的
2.可以动态地切换策略,配合配置中心,实现新功能的灰度发布和快速回滚。
3.每个策略都是独立的,可以单独进行单元测试,Mock也非常方便。
4.当需要增加一个新的业务规则或算法时,只需新增一个策略类,无需修改现有代码,极大地降低了系统的维护成本和发布风险。
5.Spring的依赖注入特性,使得策略的管理和获取变得异常简单和优雅。

3.如何使用策略模式?

那么我们就用一个非常经典的策略模式应用场景,来理解如何使用它。
这是一个多支付渠道集成的场景,我们知道,一个电商系统需要接入微信支付啊、支付宝啊、银联啊等等多种支付方式,那这种业务场景就非常适合使用咱们的策略模式。

1.定义策略接口

public interface PaymentStrategy {
    /**
     * 支付
     * @param orderId 订单ID
     * @param amount 金额
     * @param paymentData 支付附加数据
     * @return 支付结果
     */
    PayResult pay(String orderId, BigDecimal amount, Map<String, String> paymentData);

    /**
     * 支持的处理渠道类型
     * @return 渠道编码,如 "wechat", "alipay"
     */
    String supportsChannel();
}

2.实现具体的策略类

咱使用Spring的帮助,来完整地实现符合OCP开闭原则的策略模式。使用@Component注解来让Spring管理这些策略Bean。

@Component
public class WechatPaymentStrategy implements PaymentStrategy {

    @Override
    public PayResult pay(String orderId, BigDecimal amount, Map<String, String> paymentData) {
        // 调用微信支付SDK,构造微信特定的请求参数
        // ...
        System.out.println("使用微信支付...");
        // 返回微信支付的结果
        return new PayResult("SUCCESS", "微信支付成功", "wx_txn_123");
    }

    @Override
    public String supportsChannel() {
        return "wechat";
    }
}

@Component
public class AlipayPaymentStrategy implements PaymentStrategy {

    @Override
    public PayResult pay(String orderId, BigDecimal amount, Map<String, String> paymentData) {
        // 调用支付宝支付SDK
        // ...
        System.out.println("使用支付宝支付...");
        return new PayResult("SUCCESS", "支付宝支付成功", "ali_txn_456");
    }

    @Override
    public String supportsChannel() {
        return "alipay";
    }
}

3.构建策略工厂

这个工厂是模式的关键,它负责根据渠道类型,从Spring容器中获取对应的策略Bean。

@Component
public class PaymentStrategyFactory {

    // Spring会自动将所有PaymentStrategy类型的Bean注入到这个Map中
    // Key: Bean的名称, Value: 策略实例
    // 我们使用@Autowired注入一个Map,Key是bean的名字,但我们更希望通过supportsChannel()返回值来获取
    // 所以采用另一种方式:通过ApplicationContext

    @Autowired
    private ApplicationContext applicationContext;

    /**
     * 根据支付渠道获取支付策略
     * @param channel 支付渠道
     * @return 支付策略实例
     */
    public PaymentStrategy getStrategy(String channel) {
        // 从Spring容器中获取所有PaymentStrategy类型的Bean
        Map<String, PaymentStrategy> strategies = applicationContext.getBeansOfType(PaymentStrategy.class);
        
        // 遍历所有策略,找到支持当前渠道的那个
        for (PaymentStrategy strategy : strategies.values()) {
            if (strategy.supportsChannel().equalsIgnoreCase(channel)) {
                return strategy;
            }
        }
        throw new RuntimeException("未找到对应的支付策略,渠道:" + channel);
    }
}

4.在Service中使用策略模式

@Service
public class OrderService {

    @Autowired
    private PaymentStrategyFactory paymentStrategyFactory;

    public PayResult createPayment(String orderId, String channel, BigDecimal amount) {
        // 1. 验证订单等业务逻辑...,这里可以使用责任链模式去校验,可以看我之前的一期文章。
        
        // 2. 通过工厂获取策略,完全消除了if-else
        PaymentStrategy paymentStrategy = paymentStrategyFactory.getStrategy(channel);
        
        // 3.准备支付数据...
        Map<String, String> paymentData = new HashMap<>();
        // 4. 执行支付
        return paymentStrategy.pay(orderId, amount, paymentData);
    }
}

5.控制器调用

@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private OrderService orderService;

    @PostMapping("/pay")
    public PayResult payOrder(@RequestParam String orderId, @RequestParam String channel) {
        // 假设从数据库查询金额
        BigDecimal amount = BigDecimal.valueOf(100.00);
        return orderService.createPayment(orderId, channel, amount);
    }
}

那么这样子的一个小案例就写好了,它很好的结合了策略模式。
当需要新增一个“云闪付”支付时,只需创建一个 UnionPayPaymentStrategy 并实现接口,然后加上 @Component 注解即可。OrderService 和 PaymentStrategyFactory 一行代码都不需要修改。各支付渠道的逻辑完全隔离,互不影响。

4.一些技巧和最佳实践

1.与配置中心结合:策略的选择可以不硬编码,而是从Nacos、Apollo等配置中心读取。例如,可以将 payment.default.strategy=wechat 放在配置中心,实现运行时的动态切换。
2.使用注解简化工厂:可以自定义一个注解,如 @StrategyIdentifier("wechat"),然后在工厂中通过扫描注解来建立映射,避免遍历所有Bean。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface StrategyIdentifier {
    String value();
}

@StrategyIdentifier("wechat")
public class WechatPaymentStrategy implements PaymentStrategy { ... }

// 在工厂中,启动时初始化一个Map
@PostConstruct
public void init() {
    Map<String, PaymentStrategy> map = new HashMap<>();
    applicationContext.getBeansOfType(PaymentStrategy.class)
                     .forEach((beanName, strategy) -> {
        StrategyIdentifier annotation = strategy.getClass().getAnnotation(StrategyIdentifier.class);
        if (annotation != null) {
            map.put(annotation.value(), strategy);
        }
    });
    this.strategyMap = Collections.unmodifiableMap(map);
}

3.策略类最好设计成无状态的,这样它们就是线程安全的,可以被共享。所有需要的数据都通过方法参数传入.
4.不要用一个工厂处理所有类型的策略。应该为“支付”、“缓存”、“通知”等不同的领域分别建立自己的工厂和策略接口,保持单一职责。

5.小结一下:

在Java分布式项目中,策略模式远不止是一个简单的设计模式,它是一种架构思想,是应对复杂多变业务需求的非常实用的设计模式。它通过将易变的算法/规则抽象出来,实现了与稳定业务逻辑的解耦,使得系统具备了极强的可扩展性、可维护性和可测试性。熟练策略模式是非常有必要的。

最后抛出一个问题:出现 if-else 的代码,一定要使用策略模式优化么?

如果 if-else 判断分支不多并且是固定的,后续不会出现新的分支,那我们完全 可以通过抽函数的方式降低程序复杂性;不要想法设法去除 if-else 语句,存在即合理。而且,使用策略模式会导致类增多,没有必要为了少量的判断分支引入策略模式。所以还是那句话,并不是为了使得代码优雅就一定要引入设计模式,设计模式的确可以使代码变得更优雅,但是同样存在一些问题,比如代码不易读呀,代码变得更臃肿呀等等,所以,任何的设计模式的引入与否都是得看你具体的业务是否有必要引入才去引入的,随意的就引入那就是在耍流氓。这就有点像“为赋新词强说愁”了。

好的,那么今天的内容到这里就结束了,如果对您有帮助,可以点个赞支持一下,我们下期再见!

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐