• 设计模式之策略模式


            “定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。”


    1 简介

            假如说现在需要实现个方法,获取订单商品的价格。因为订单类型的不同,计算价格的方式也会有所不同。常规的写法如下所示:

    1. public BigDecimal getOrderPrice(String subOrderSn, Long skuId) {
    2. //获取子单
    3. OrderSubDO orderSubDO = orderSubMapper.findBySubOrderSn(subOrderSn);
    4. //获取子单商品
    5. List orderDetailDOS = orderDetailMapper.selectBySubOrderSn(subOrderSn);
    6. OrderDetailDO orderDetailDO = orderDetailDOS.stream().filter(item -> item.getSkuId().equals(skuId)).findFirst().orElse(null);
    7. OrderTypeEnum orderTypeEnum = OrderTypeEnum.getByCode(orderSubDO.getOrderType());
    8. switch (orderTypeEnum) {
    9. //实物订单
    10. case DEFAULT:
    11. return orderDetailDO.getPrice();
    12. //卡券订单
    13. case COUPON:
    14. return orderDetailDO.getPrice().multiply(new BigDecimal("1.1"));
    15. //会员订单
    16. case VIP:
    17. return orderDetailDO.getPrice().multiply(new BigDecimal("0.9"));
    18. default:
    19. return null;
    20. }
    21. }

            实物订单的商品取原价,卡券订单的商品取原价*1.1,会员订单的商品取原价*0.9。上面只是一个简单实现的demo,计算价格的代码只有一行。而如果计算订单商品价格的逻辑比较复杂的话,这个方法将会变得异常臃肿,而且也不利于扩展。

            这个时候我们就可以使用策略模式来进行重构,不同计算商品价格的逻辑会抽象出一个策略类来。

    抽象策略(Abstract Strategy):通常由一个接口或一个抽象类实现,里面定义了一些抽象的规范;
    具体策略(Concrete Strategy):具体的算法和行为;
    环境(Context):持有策略类的应用,负责将客户端请求委派给具体的策略对象执行。        

            通常我们在遇到有多个分支的代码,可以抽象成统一的调度层的时候,可以考虑使用策略模式来实现。


    2 示例

            再举个例子,假如说现在需要实现一个支付的功能,因为支付有很多的接入渠道(微信支付、支付宝、XX银行等等),每种渠道又可以抽象出相同的支付功能,所以很适合用策略模式来实现。

            首先需要实现一个枚举:

    1. public enum PayEnum {
    2. WECHAT_PAY(1, "微信支付"),
    3. ALIPAY(2, "支付宝支付"),
    4. BANK(3, "XX银行支付");
    5. private final Integer code;
    6. private final String desc;
    7. PayEnum(Integer code, String desc) {
    8. this.code = code;
    9. this.desc = desc;
    10. }
    11. public Integer getCode() {
    12. return code;
    13. }
    14. public String getDesc() {
    15. return desc;
    16. }
    17. public static PayEnum getByCode(Integer code) {
    18. return Arrays.stream(values()).filter(item -> item.getCode().equals(code)).findFirst().orElse(null);
    19. }
    20. }

            接着写一个支付抽象类:

    1. public abstract class AbstractPay {
    2. public abstract PayEnum type();
    3. /**
    4. * 预支付
    5. */
    6. public abstract String prePay();
    7. /**
    8. * 支付
    9. */
    10. public abstract String pay();
    11. }

            分别实现微信支付、支付宝支付和银行支付:

    1. @Service
    2. public class WechatPay extends AbstractPay {
    3. @Override
    4. public PayEnum type() {
    5. return PayEnum.WECHAT_PAY;
    6. }
    7. @Override
    8. public String prePay() {
    9. return "微信预支付";
    10. }
    11. @Override
    12. public String pay() {
    13. return "微信支付";
    14. }
    15. }
    1. @Service
    2. public class AliPay extends AbstractPay {
    3. @Override
    4. public PayEnum type() {
    5. return PayEnum.ALIPAY;
    6. }
    7. @Override
    8. public String prePay() {
    9. return "支付宝预支付";
    10. }
    11. @Override
    12. public String pay() {
    13. return "支付宝支付";
    14. }
    15. }
    1. @Service
    2. public class BankPay extends AbstractPay {
    3. @Override
    4. public PayEnum type() {
    5. return PayEnum.BANK;
    6. }
    7. @Override
    8. public String prePay() {
    9. return "XX银行预支付";
    10. }
    11. @Override
    12. public String pay() {
    13. return "XX银行支付";
    14. }
    15. }

            为了便于演示,这里具体的支付实现就用打印一句话来代替了。

            有了具体的支付实现后,我们还需要一个工厂类:

    1. @Component
    2. public class PayFactory {
    3. public AbstractPay getStrategy(Integer code) {
    4. PayEnum payEnum = PayEnum.getByCode(code);
    5. if (payEnum == null) {
    6. return null;
    7. }
    8. switch (payEnum) {
    9. case WECHAT_PAY:
    10. return new WechatPay();
    11. case ALIPAY:
    12. return new AliPay();
    13. case BANK:
    14. return new BankPay();
    15. default:
    16. return null;
    17. }
    18. }
    19. }

            这样我们就可以通过调用PayFactory.getStrategy方法来获取到相应的策略类,之后再调用相应的支付方法就行了。不需要去理解每种策略的实现,只需要传入支付类型即可。

            但是上面的工厂类实现还有个问题:如果策略有很多,并且会频繁更改的话,那么每次都需要新加一种case条件,这样不利于扩展。我们只是把if-else条件挪到了工厂类里面而已,并没有发生实质性的变化,该改的还是会改。所以这里我们改造一下,利用Spring相关的功能进行重构:

    1. @Component
    2. public class PayConfig implements InitializingBean {
    3. @Autowired
    4. private ApplicationContext applicationContext;
    5. private static final Map STRATEGY_MAP = Maps.newHashMapWithExpectedSize(3);
    6. @Override
    7. public void afterPropertiesSet() {
    8. Map beans = applicationContext.getBeansOfType(AbstractPay.class);
    9. for (Map.Entry entry : beans.entrySet()) {
    10. AbstractPay bean = entry.getValue();
    11. STRATEGY_MAP.put(bean.type(), bean);
    12. }
    13. }
    14. public AbstractPay getStrategy(PayEnum payEnum) {
    15. return STRATEGY_MAP.get(payEnum);
    16. }
    17. }

            applicationContext.getBeansOfType方法可以动态获取到抽象类的所有实现类。而PayConfig实现了InitializingBean接口,在项目启动的时候会执行其中的afterPropertiesSet方法。该方法中会把所有的策略类缓存成一个map,之后在使用的时候直接调用PayConfig.getStrategy方法就行了。这样的话如果以后需要增加或删除策略类,也不需要更改PayConfig类中的代码,每次项目启动的时候都会重新获取当前最新的策略类。

            调用的demo如下所示:

    1. AbstractPay strategy = payConfig.getStrategy(PayEnum.WECHAT_PAY);
    2. String prePay = strategy.prePay();
    3. String pay = strategy.pay();
  • 相关阅读:
    代码随想录训练营第53天|1143.最长公共子序列,1025.不相交的线,53.最大子数组和
    最后一篇博客
    细胞凋亡通路 | MedChemExpress
    react-navigation 6.x 学习(3)
    SQL开窗函数
    springboot整合ELK
    ThreadLocal源码解析学习
    汇编原理(二)寄存器——CPU工作原理
    github.com/holiman/uint256 源码阅读
    【VsCode】VsCode的安装与42个插件大全
  • 原文地址:https://blog.csdn.net/weixin_30342639/article/details/133325874