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

简介: 策略模式,英文全称是 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.

image.png

该图片由daschorsch在Pixabay上发布


你好,我是看山。


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


定义

策略模式,英文全称是 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 分支代码。那减少分支代码有什么好处呢?


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

减少 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,还是微调传输参数,都会影响整个支付逻辑,从而增加了在已有正常运行代码中引入错误的风险。

image.png



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


解决方案

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


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


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


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

image.png



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


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


适用与不适用

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


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

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

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

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


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

如果使用了 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 当中,借助自动注入,上下文甚至可以不知道具体策略实现。


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


推荐阅读

Java 中的单例模式(完整篇)

设计模式:建造者模式

目录
相关文章
|
1月前
|
设计模式 算法 PHP
php设计模式--策略模式(六)
php设计模式--策略模式(六)
11 0
|
4月前
|
设计模式 算法 搜索推荐
设计模式之策略模式
设计模式之策略模式
42 0
|
4月前
|
设计模式 算法 Java
行为型设计模式-策略模式(Strategy Pattern)
行为型设计模式-策略模式(Strategy Pattern)
|
14天前
|
设计模式 算法 Java
Java 设计模式:探索策略模式的概念和实战应用
【4月更文挑战第27天】策略模式是一种行为设计模式,它允许在运行时选择算法的行为。在 Java 中,策略模式通过定义一系列的算法,并将每一个算法封装起来,并使它们可以互换,这样算法的变化不会影响到使用算法的客户。
21 1
|
4月前
|
设计模式 算法 Java
【设计模式】策略模式在数据接收和发送场景的应用
在数据接收和发送场景打算使用了if else进行判断。ASystem.sync("向A同步数据");BSystem.sync("向B同步数据");...非常麻烦,需求多了很臃肿!策略模式(Strategy Pattern)定义了一组同类型的算法,在不同的类中封装起来,每种算法可以根据当前场景相互替换,从而使算法的变化独立于使用它们的客户端(即算法的调用者)。// 创建两个策略对象// 创建上下文对象,并传入策略对象。
61 1
|
5月前
|
设计模式 算法
设计模式思考,简单工厂模式和策略模式的区别?
设计模式思考,简单工厂模式和策略模式的区别?
|
5月前
|
设计模式 Java
细说一下设计模式中的策略模式!
细说一下设计模式中的策略模式!
31 0
|
1月前
|
设计模式 算法 搜索推荐
23种设计模式,策略模式的概念优缺点以及JAVA代码举例
【4月更文挑战第10天】设计模式是软件工程中的一种最佳实践指导,用于解决常见的设计问题。它们被分类为创建型、结构型和行为型模式。其中,策略模式是一种行为型设计模式,旨在定义一系列算法,封装每一个算法,并使它们可互换。策略模式允许算法独立于使用它们的客户端变化
22 1
|
2月前
|
设计模式 算法
策略模式--设计模式
策略模式--设计模式
17 0
|
2月前
|
设计模式 算法 Java
【设计模式】策略模式
【设计模式】策略模式