写点什么

人人都会设计模式:策略模式

作者:看山
  • 2022 年 1 月 22 日
  • 本文字数:2957 字

    阅读完需:约 10 分钟

人人都会设计模式:策略模式

你好,我是看山。


本文收录在《一个架构师的职业素养》专栏,日拱一卒,功不唐捐。

定义

策略模式,英文全称是 Strategy Design Pattern。在 GoF 的《设计模式》一书中,它是这样定义的:


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


翻译成中文就是:定义一族算法类,将每个算法分别封装起来,让它们可以互相替换。策略模式可以使算法的变化独立于使用它们的客户端。


这里所说的客户端代指使用算法的代码。


根据使用场景分类,策略模式是一种行为型模式,用于运行时控制类的行为或算法。


使用上的直观感受是,策略模式可以减少了 if-else/switch 分支代码。那减少分支代码有什么好处呢?


  1. 解耦代码,策略模式就是解耦不同算法实现;

  2. 减少 bug 产生概率,减少分支,就是减少 bug 发生概率。


有编程经验的都知道,很多 bug 都是从分支逻辑产生的。我刚开始工作时晚上 12 点开始抓虫,一直抓到凌晨 2 点多,最后发现是有一个 if-else 分支中,在某个 if 前面少写了一个 else。下面是示例,实际代码比这个复杂很多:


if (a < 1) {} else if (a < 2) {} else if (a < 3) {}
复制代码


结果写成了:


if (a < 1) {} else if (a < 2) {} if (a < 3) {}
复制代码


代码编译不会错,但是在执行时,某些 case 会不符合预期。

问题

我们来看看策略模式出现的场景。


以电商系统的支付功能为例,最早的时候,我们可能为了更快上线,选择一个较多人使用的支付方式,比如微信支付(也有可能是支付宝支付,根据售卖场景不同区分)。这个时候,我们只需要判断用户是从 PC 页面进入还是 H5 进入即可。


后来,业务发展比较好,涉及人群更多了,于是需要对接支付宝支付。支付宝支付也分为了多种的支付场景,对接接口变多了,但是也在可控范围内。


再后来,我们需要对接银联支付、对接各银行接口,等等,支付接口变得越来越臃肿。于是,每对接一种支付方式,支付相关接口就会增加一倍。此时,这坨臃肿的代码,无论是修复简单的 bug,还是微调传输参数,都会影响整个支付逻辑,从而增加了在已有正常运行代码中引入错误的风险。



如果是多人协作开发,我们还会陷入代码合并时应付各种冲突的情况。终于,在某一时刻,我们看着这一坨代码,已经无从下手维护了。

解决方案

首先,我们来分析一下上面的场景,不变的是系统内部的支付业务逻辑,变化的是支付方式。


支付方式的可变性在于,可能会与多种支付方式对接,对接参数、协议、地址等都会不同。根据设计模式的整体思想,我们将变化的单独出去,将不变的稳定下来。


这种处理方式就是策略模式建议的:找出负责用许多不同方式完成特定任务的类,然后将其中的算法抽取到一族被称为策略的独立类中。


调用这些策略类的是调用上下文,它持有对所有策略类的引用。上下文不执行任务,它是任务的指挥者,将工作委派给已连接的策略对象。关系如下:



很多教程到这里就结束了,如果你能够看到这里,而且还用心看了,你就会发现一丝丝的不一样。


根据迪米特法则(LOD,Law of Demeter),上下文不需要知道具体策略类的功能,只需要通过特定的接口,用于触发选中策略即可。也就是说,完整的策略模式,应该有具体的策略判断是否由该策略执行,上下文只需要知道有哪些策略就行了。这样改动之后,上下文还能够与工厂模式结合。如果策略是无状态策略,还可以在上下文中引入单例模式。

适用与不适用

根据上面的定义,策略模式是围绕可以互换的算法来创建业务的。简单的说就是,分支逻辑隔离。


  1. 当你想使用各种不同算法变体,且能够在运行时切换算法。策略模式可以将对象关联到不同实现方式的不同子任务中,可以间接的修改对象;

  2. 只有在执行时有些许不同的相似算法。可以将不同的行为抽象到独立的类中,在原来的类中调用这些独立的算法;

  3. 算法在业务逻辑中不是特别重要。我们可以通过策略模式将算法、数据、依赖等抽离出来,在运行时调用即可。


设计模式只是解决问题的优雅实现,并不一定适用所有情况,比如下面这几种,就可以不用非得实现策略模式:


  1. 如果算法极少变化,就没有任何理由引入新的类和接口。

  2. 如果使用了 Java8 之后的版本,可以使用函数式编程,有时候就使用 Lambda 表达式或者匿名内部类的方式实现具体算法即可。

示例代码

还是以支付为例,因为都是演示,一切从简。我曾经主导过支付中台,如果想要具体实现,可以具体聊一下。


首先定义支付策略接口:


public interface PayStrategy {    String payType();
void callPay(BigDecimal amount);}
复制代码


payType()是在具体的策略实现中定义策略可执行的支付方式,也可以通过传参数的方式返回boolean类型用于判断是否可执行。


然后是微信支付和支付宝支付分别实现支付策略接口:


public class WxpayPayStrategy implements PayStrategy {
@Override public String payType() { return "WXPAY"; }
@Override public void callPay(BigDecimal amount) { // 微信支付接口 // 这里只是演示,即使都是微信支付,也会分不同的接口 System.out.println("调用微信支付接口"); }}
public class AlipayPayStrategy implements PayStrategy {
@Override public String payType() { return "ALIPAY"; }
@Override public void callPay(BigDecimal amount) { // 调用支付宝支付接口 // 这里只是演示,即使都是支付宝支付,也会分不同的接口 System.out.println("调用支付宝支付接口"); }}
复制代码


我们再来看看持有策略算法的上下文:


public class StrategyContext {    private static final Map<String, PayStrategy> PAY_STRATEGY_MAP = new HashMap<>();
static { final AlipayPayStrategy alipayPayStrategy = new AlipayPayStrategy(); final WxpayPayStrategy wxpayPayStrategy = new WxpayPayStrategy();
PAY_STRATEGY_MAP.put(alipayPayStrategy.payType(), alipayPayStrategy); PAY_STRATEGY_MAP.put(wxpayPayStrategy.payType(), wxpayPayStrategy); }
public void pay(String payType, BigDecimal amount) { final PayStrategy payStrategy = PAY_STRATEGY_MAP.get(payType); payStrategy.callPay(amount); }}
复制代码


可以看到,上下文只需要知道策略算法的存在,至于算法是否符合要求,由算法自己判断。


调用就比较简单了:


public class Main {    public static void main(String[] args) {        final StrategyContext strategyContext = new StrategyContext();        strategyContext.pay("ALIPAY", BigDecimal.TEN);        strategyContext.pay("WXPAY", BigDecimal.ONE);    }}
复制代码

文末总结

策略模式可能用来减少分支逻辑,将不同的算法分离开来。如果配合工厂模式、单例模式,可以更加灵活的使用。如果是在 Spring 当中,借助自动注入,上下文甚至可以不知道具体策略实现。


最近刚看到一句话,“日拱一卒,功不唐捐”。坚持下去,每天学点新东西,给生活加点色彩。


青山不改,绿水长流,咱们下次见。

推荐阅读


你好,我是看山。游于码界,戏享人生。如果文章对您有帮助,请点赞、收藏、关注。

发布于: 刚刚阅读数: 2
用户头像

看山

关注

🏆 InfoQ写作平台-签约作者 🏆 2017.10.26 加入

InfoQ签约作者,CSDN 博客专家,公号「看山的小屋」,专注后端开发、架构相关知识分享,个人网站 https://howardliu.cn/。

评论

发布
暂无评论
人人都会设计模式:策略模式