最近几天好好补了下血,才恢复了点精力。所以有了一点写些啥的欲望,那就写一下设计模式好了。
设计模式,相信大家应该都或多或少的接触过。总的来说,设计模式是一些前辈们在开发过程中面对一些常见问题抽象出来的通用解决方案,而且也经过了相当长时间的验证,普适性极强,对于大多数问题基本上都能解决。当然如果自己的业务确实比较特别,那现有的设计模式可能也确实无法满足。而且咱们这个行业也是在发展的,现在适用的设计模式可能过一段时间就不怎么适用了,也可能有些大佬发现了更好的设计抽象出来之后成了另一种设计模式。当然,现在的真实情况大多数是对多个设计模式的组合以及变种等。
注意:设计模式不能滥用哦,不合理的使用设计模式反而会使代码变得更加复杂,所以不要炫技。
介绍了这么多,那么什么是策略模式呢?顾名思义,就是当同一个行为可以有不同的策略的时候,通过某种条件(策略)选择某一个具体的策略实现执行方法。比如支付,支付本身是一个行为,但是可以选择微信支付、支付宝支付、银联支付和聚合支付等多种策略,这种就可以使用策略模式。可以减少很多if else语句块。
策略模式的优缺点:
优点:
- 避免多重的条件判断
- 易扩展
缺点:
- 代码量增多
- 策略实现需要对外暴露
可以先看看如果没有使用策略模式,那我们在遇到扩展时是怎么处理的。就拿支付来举例吧。伪代码如下:
public String doPay(PayParam payParam) { // 先验 preCheck(payParam); String result = null; if(payParam.getPayChannelEnum() == PayChannelEnum.ALI) { // 支付宝支付的一些校验和处理 result = doAli(payParam); } else if (payParam.getPayChannelEnum() == PayChannelEnum.WECHAT) { // 微信支付的一些校验和处理 result = doWechat(payParam); } else { // 其他情况的处理 result = null; } // 结果处理 result = resultHandle(result); return result; }
这个时候如果又加上了银联支付,那么就得在这个if else的分支上进行修改,再加一个分支,如果我们的处理不只是这么简单的话,甚至还涉及到代码结构的调整,先验和结果处理调整,这不符合代码封装的开闭原则。而且也会改得很令人头痛。
策略实现
使用策略模式实现也是有好几种方法的,下面我就来讲讲我在真实场景中使用过的策略实现吧。
第一种
这种方式非常简单,简直就是上面的if else套了个封装的壳子而已。定义一个接口,然后接口有几个实现,在根据if else判断需要构造哪个实现。然后调用执行方法。
// 策略接口类,用于抽象支付的行为 public interface PayStrategy { String doPay(PayParam payParam); } // 具体支付宝支付的实现 public class AliPayStrategyImpl implements PayStrategy { @Override public String doPay(PayParam payParam) { return "alipay"; } } // 具体微信支付的实现 public class WechatPayStrategyImpl implements PayStrategy { @Override public String doPay(PayParam payParam) { return "wechatpay"; } } // 调用支付的入口 public class OrderPay { private void preCheck(PayParam payParam) { } private String resultHandle(String result) { // do something return "result"; } public String doPay(PayParam payParam) { // 先验 preCheck(payParam); PayStrategy payStrategy = null; if(payParam.getPayChannelEnum() == PayChannelEnum.ALI) { payStrategy = new AliPayStrategyImpl(); } else if (payParam.getPayChannelEnum() == PayChannelEnum.WECHAT) { payStrategy = new WechatPayStrategyImpl(); } String result = payStrategy == null ? null : payStrategy.doPay(payParam); // 结果处理 result = resultHandle(result); return result; } }
这种如果需要扩展的话,就是再加一个实现类,然后if else这里构造实现类的地方再增加一个分支,其实也没有完全解决掉对修改关闭的问题。当然如果将bean交给spring管理的话又会方便很多。
第二种(推荐)
这种就比较有趣了,是使用枚举类来进行多策略的实现。下面我们就一起来看看吧。
public enum PayChannelEnum { ALI() { @Override public String doPay(PayParam payParam) { return "alipay"; } }, WECHAT() { @Override public String doPay(PayParam payParam) { return "wechatpay"; } }, ; public abstract String doPay(PayParam payParam); }
这种方式,代码看着很简洁,逻辑也很清晰,使用也很方便;但是有一个缺点,如果你需要用到spring注入的对象的话,这种方式就无法工作了。
第三种
接下来这种就需要依赖于Spring的IOC和DI了,这两者是啥我这里就不展开介绍了,如果不了解的小伙伴,可以去搜索一下,大致的就是帮我们管理实现类的,类构造和注入等。
// 策略接口类,用于抽象支付的行为。依然需要一个策略接口用来抽象支付的行为 public interface PayStrategy { String doPay(PayParam payParam); } // 具体支付宝支付的实现 @Component("ALI") // 重点,需要指定实例bean时的名称 public class AliPayStrategyImpl implements PayStrategy { @Override public String doPay(PayParam payParam) { return "alipay"; } } // 具体微信支付的实现 @Component("WECHAT") // 重点,需要指定实例bean时的名称 public class WechatPayStrategyImpl implements PayStrategy { @Override public String doPay(PayParam payParam) { return "wechatpay"; } } // 调用支付的入口 @Component // 重点 public class OrderPay { @Autowired // 重点 private Map<String , PayStrategy> payStrategyMap; private void preCheck(PayParam payParam) { // do something check } private String resultHandle(String result) { // do something return "result"; } public String doPay(PayParam payParam) { preCheck(payParam); String result = payStrategyMap.get(payParam.getPayChannelEnum().name()).doPay(payParam); return resultHandle(result); } }
当然这里我们是没有考虑那些异常情况,以及策略未找到之类的兜底或者处理等。实际编程是需要都考虑的。
可以看到在这段实现中我是标了几个重点。
- 首先是各自的实现类需要指定bean名称(策略指定的枚举名称),这是为了在依赖注入的时候方便我们匹配用的
- 其次是在调用的入口OrderPay一定也需要加上Spring的bean声明注解,这是为了将bean统一交给Spring管理,才能将策略实现注入
- payStrategyMap这个域对应的是个map,key为实例名,value为对应的实例。这样在调用doPay时就可以根据传入的策略枚举找到实例然后执行具体方法。
缺点也很明显,需要指定实例名称,且需要是一个静态常量,所以也不能直接使用枚举的名称。这样容易出现两个问题。
- 实例名称如果和其他实例冲突会导致启动服务就报错,那换个实例名称的话,枚举也得跟着改动,容易引起其他依赖的问题。
- 枚举的name和实例名称需要一一对应,如果有哪里大小写或者字符敲错了等问题,不太容易排查。
第四种(推荐)
这种目前是我常用的一种策略实现,不过呢,如果策略本身比较少的话,写这个轮子可能会觉得比较浪费,因为本身的架子代码就挺多的了。话不多说,show you the code。
// 很熟悉了吧?一定需要的一个策略接口 public interface PayStrategy { String doPay(PayParam payParam); } // 重要。实现策略的一个抽象类。用于定义所有的策略实现的基类 public abstract class AbstractPayStrategy implements PayStrategy { // 重要。新定义一个策略实现类需要实现的策略。返回对应的枚举 public abstract PayChannelEnum strategy(); } // 基础实现,注意,此处是实现顶上的策略接口。在调用入口地方也是注入此实现。 @Component @Primary // 重要。用于注入时优先选择此实例 public class PayStrategyImpl implements PayStrategy { private Map<PayChannelEnum, AbstractPayStrategy> payStrategies; // 构造器注入。然后转化成map存储。 public PayStrategyImpl(List<AbstractPayStrategy> payStrategyList) { payStrategies = payStrategyList.stream().collect(Collectors.toMap(AbstractPayStrategy::strategy, Function.identity())); } // 实际的支付接口都是调用此方法 @Override public String doPay(PayParam payParam) { return payStrategies.get(payParam.getPayChannelEnum()).doPay(payParam); } } // 注意这里的改动,此处是继承抽象类,并且实现返回策略枚举的接口 @Component public class AliPayStrategyImpl extends AbstractPayStrategy { @Override public String doPay(PayParam payParam) { return "alipay"; } @Override public PayChannelEnum strategy() { return PayChannelEnum.ALI; } } @Component public class WechatPayStrategyImpl extends AbstractPayStrategy { @Override public String doPay(PayParam payParam) { return "wechatpay"; } @Override public PayChannelEnum strategy() { return PayChannelEnum.WECHAT; } }
到此整体的架子就已经搭建好了,而具体的调用入口也是很简单的一个注入即可。
@Autowired private OrderPay orderPay;
这种实现代码量很多,但是基本上模糊了选择策略的那部分逻辑,而且有个很重要的优点是只需要维护一个策略枚举,能很方便的将实现与策略枚举映射起来。
其中用到的知识点有:
- 构造器注入。PayStrategyImpl有定义一个List参数的构造方法,实例化该类时会自动将Spring管理的策略bean注入进去
- Spring的Primary注入,当接口有多个实现时,使用Autowired注解的话,Spring并不知道选择哪个bean注入,所以如果加上Primary时就可以给Spring决策,优先注入那个实例。
类图如下:
最后
关于策略模式的多种实现,到这里就算结束了。也是有写几个我平常有使用的策略模式实现,当然还有一些其实可以改造一下形成自己代码风格的实现方式,比如第三种,可以再定义一个Map,在bean实例化时将自身put到Map中(可以通过@PostConstruct注解的方式,或者实现InitializingBean的方式等),然后剩下的工作就水到渠成了。
每种实现其实都是有一些优缺点的,也并不一定适用于所有人和所有场景,希望大家能够找到符合自己代码风格的实现思路,也希望大家能够通过这一节对策略模式有一个清晰一点的认识,在实际工作中能够更简洁清晰的写自己的bug咯~。那就这样吧,谢谢大家的观看,我们下期见。
标签:多种,doPay,String,payParam,模式,PayParam,public,策略 From: https://www.cnblogs.com/aischen/p/16758692.html