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

简介: 策略模式,英文全称是 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 中的单例模式(完整篇)

设计模式:建造者模式

目录
相关文章
|
2月前
|
设计模式 算法 测试技术
PHP中的设计模式:策略模式的应用与实践
在软件开发的浩瀚海洋中,设计模式如同灯塔,指引着开发者们避开重复造轮子的暗礁,驶向高效、可维护的代码彼岸。今天,我们将聚焦于PHP领域中的一种重要设计模式——策略模式,探讨其原理、应用及最佳实践,揭示如何通过策略模式赋予PHP应用灵活多变的业务逻辑处理能力,让代码之美在策略的变换中熠熠生辉。
|
11天前
|
设计模式 算法 Kotlin
Kotlin教程笔记(53) - 改良设计模式 - 策略模式
Kotlin教程笔记(53) - 改良设计模式 - 策略模式
36 2
|
1月前
|
设计模式 算法 Kotlin
Kotlin教程笔记(53) - 改良设计模式 - 策略模式
本教程详细讲解Kotlin语法,适合深入学习。快速入门可参考“简洁”系列教程。本文通过游泳运动员的案例,介绍策略模式及其在Kotlin中的改良应用,利用高阶函数简化代码结构,提高灵活性。
30 3
|
1月前
|
设计模式 算法 Kotlin
Kotlin教程笔记(53) - 改良设计模式 - 策略模式
本教程详细讲解Kotlin语法,适合深入学习。快速入门可参考“简洁”系列教程。本文介绍策略模式在Kotlin中的应用,通过游泳运动员的例子,展示如何使用接口和高阶函数实现策略模式,使代码更简洁、灵活。
28 2
|
1月前
|
设计模式 算法 Kotlin
Kotlin教程笔记(53) - 改良设计模式 - 策略模式
Kotlin教程笔记(53) - 改良设计模式 - 策略模式
62 3
|
1月前
|
设计模式 算法 Kotlin
Kotlin教程笔记(53) - 改良设计模式 - 策略模式
Kotlin教程笔记(53) - 改良设计模式 - 策略模式
27 3
|
1月前
|
设计模式 算法 PHP
PHP中的设计模式:策略模式的深入解析与实践
【10月更文挑战第9天】 策略模式是一种行为设计模式,它允许在运行时选择算法的行为。在PHP开发中,通过使用策略模式,我们可以轻松切换算法或逻辑处理方式而无需修改现有代码结构。本文将深入探讨策略模式的定义、结构以及如何在PHP中实现该模式,并通过实际案例展示其应用价值和优势。
29 1
|
1月前
|
设计模式 算法 PHP
PHP中的设计模式:策略模式的深入解析与应用
【10月更文挑战第8天】 在软件开发的浩瀚宇宙中,设计模式如同星辰指引,照亮了代码设计与架构的航道。本文旨在深入探索PHP语境下策略模式(Strategy Pattern)的精髓,不仅剖析其内核原理,还将其融入实战演练,让理论在实践中生根发芽。策略模式,作为解决“如何优雅地封装算法族”的答案,以其独特的灵活性与扩展性,赋予PHP应用以动态变换行为的能力,而无需牵动既有的类结构。
24 2
|
1月前
|
设计模式 算法 PHP
PHP中的设计模式:策略模式的深入解析与实践
在PHP开发中,设计模式是提高代码可读性、可维护性和扩展性的重要工具。本文将深入探讨策略模式这一行为型设计模式,通过分析其定义、结构、使用场景以及在PHP中的实际应用,帮助开发者更好地理解和运用策略模式来优化自己的项目。不同于传统摘要的简洁概述,本文摘要部分将详细阐述策略模式的核心理念和在PHP中的实现方法,为读者提供清晰的指引。
|
1月前
|
设计模式 算法 PHP
PHP中的设计模式:策略模式的深入解析与实践
策略模式是一种行为型设计模式,用于定义一系列算法,将每种算法都封装起来,并使它们可以互换。本文将探讨如何在PHP中实现策略模式,以及如何利用它来提高代码的灵活性和可维护性。通过具体示例,我们将看到策略模式在处理复杂业务逻辑时的优势,从而帮助开发者编写出更加清晰、易于扩展的代码。