点击上方蓝色“程序猿DD”,选择“设为星标”

回复“资源”获取独家整理的学习资料!

来源:公众号「闻人的技术博客」

前言

前段时间,接到一个需求:开发一个聚合支付服务,对其他内部项目提供统一的接口来实现不同支付平台的支付能力发起,比如支付宝,微信,银联等。为了处理相似的支付操作而各平台具体实现不同的情况,要让各个平台接口能力能相互独立,并要方便扩展后续新增的支付平台,我引入了设计模式的策略模式来应对需求场景,借此深入学习总结下策略模式,于是也就有了本文,希望对学习策略模式的同学有所帮助。

为什么需要策略模式

日常工作开发中我们总会遇到如下熟悉的代码片段:

if(condition1){// do something1
} else if (condition2){// do something2
} else if (condition3){// do something3
}

在每个 if 条件下都有数十行甚至百行的业务处理,各自处理又是相互独立的并且目的一致,都汇聚在一个方法里。这样的写法不但让类变得臃肿冗长,并且不同逻辑都在一个类中修改,维护和扩展起来都很费劲。那么又有什么办法可以优化这大段的代码呢,在实现功能的同时,让代码更加灵活和易维护。

要解决这个问题,本文的主角—策略模式 就登场了,作为设计模式中比较简单的行为型模式,其实很多框架中都见到它的身影,稍后我们也会从各框架源码中识别策略模式的应用。使用策略模式可以帮助我们将每个处理逻辑封装成独立的类,客户端类需要进行哪种处理逻辑就使用对应的类,调用其封装了业务处理细节的方法即可。这样一来,客户端类减少了业务处理逻辑的大量代码,让自身更加精简。当业务逻辑有所改动时,只要在对应的类中修改,而不影响其他的类;并且如果出现了新的业务逻辑只要新增相似的类进行实现,供客户端类调用即可。

什么是策略模式

接下来我们就介绍下策略模式的定义和组成,以及它的基本形式。

首先看下维基百科上策略模式的定义:

In computer programming, the strategy pattern (also known as the policy pattern) is a behavioral software design pattern) that enables selecting an algorithm at runtime. Instead of implementing a single algorithm directly, code receives run-time instructions as to which in a family of algorithms to use.

策略模式也叫政策模式,允许程序在运行时选择一个算法执行,通常存在一类算法实现提供外部选择执行,这里的算法,也可以叫做策略,相当于上节内容提到的具体处理逻辑。

再来看下 《设计模式:可复用面向对象软件的基础》一书中对策略模式的定义:

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

再次对其定义解读:定义一类算法,各自独立封装实现,并且相互之间是可替换的。除此之外,由客户端类决定具体使用哪个算法。

上述两个定义都提到了算法一词,它表示了完整的,不可再拆分的业务逻辑处理。通常用接口或者抽象类来表示一类算法的抽象,提供多种对该类算法的操作实现,以此组成一类独立且可替换的算法,也叫策略组。

了解完定义后,我们再来看下策略模式通用类图:

类图中涉及三类角色:Context,Strategy 和 ConcreteStrategy

  • Strategy:抽象策略角色,代表某个算法的接口或者抽象类,定义了每个算法或者策略需要具有的方法和属性。

  • Context:上下文角色,引用策略接口对象,屏蔽了外部模块对策略或方法的直接访问,只能通过Context 提供的方法访问。

  • ConcreteStrategy:抽象策略的具体实现,该类含有具体的算法,并且通常不只一种实现,有多个类。

这三个角色的功能职责都十分明确,对应的源码实现也十分简单,现在我们就来快速看下每个角色对应的通用源码。

// 抽象的策略角色
public interface Strategy {void doSomething();
}// 具体策略角色
public class ConcreteStrategy implements Strategy {@Overridepublic void doSomething() {System.out.println("ConcreteStrategy doSomething !");}
}// 上下文角色
public class Context {private final Strategy strategy;public Context(Strategy strategy) {this.strategy = strategy;}public void doAnything() {this.strategy.doSomething();}
}

有了策略模式的基本代码结构,在客户端类中使用十分简单,想要哪个策略,就产生出它的具体策略对象放入上下文对象内,然后由上下文对象执行具体策略操作即可,具体代码如下:

public class Client {public static void main(String[] args) {Strategy strategy = new ConcreteStrategy();Context context = new Context(strategy);context.doAnything(); // ConcreteStrategy doSomething !}
}

识别策略模式

看清楚了策略模式的定义,角色组成以及通用的代码结构之后,我们就来看下策略模式在通用框架里的应用,来加深对策略模式的认识。

JDK 与策略模式

在常用的Java 集合框架中,比较器 java.util.Comparator 的设计就采用了策略模式。Comparator 就是一个抽象的策略接口,只要一个类实现这个接口,自定 compare 方法,该类成为具体策略类,你可以在很多地址找到这个抽象策略接口的实现,官方在工具类 java.util.Comparators 里也提供 NaturalOrderComparator,NullComparator 两种具体策略类。而使用 Comparator 到的 java.util.Collections 类就是 Context 角色,将集合的比较功能封装成静态方法对外提供。

Spring Framework 与策略模式

Spring 框架最早以 IoC 和 DI 两大特性著称,不需要开发者自己创建对象,而是通过 Spring IoC 容器识别然后实例化所需对象。在 Spring 中将执行创建对象实例的这个操作封装为一种算法,用接口类 org.springframework.beans.factory.support.InstantiationStrategy 进行声明,而具体策略类则有 org.springframework.beans.factory.support.SimpleInstantiationStrategy 和 org.springframework.beans.factory.support.CglibSubclassingInstantiationStrategy 两个,并且 CglibSubclassingInstantiationStrategy 是对 SimpleInstantiationStrategy 的继承扩展,也是 Spring 容器中真正使用到的策略类,具体应用的源码可参考 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory 类:

/*** Instantiate the given bean using its default constructor.* @param beanName the name of the bean* @param mbd the bean definition for the bean* @return a BeanWrapper for the new instance*/
protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) {//...beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);//...
}

如何使用策略模式

实例应用

俗话说学以致用,接触了策略模式后我们应该想想怎么用在自己日常开发项目中呢,这里就简单通过一个实例来说明下策略模式的使用方式。假设现在有个需求:需要对一个目录或者文件实现两种不同格式的解压缩方式:zip压缩和gzip压缩,也后续可能新增其他的解压缩方式。

我们首先将解压缩的算法抽象成抽象策略接口 CompressStrategy, 提供压缩方法 compress 和解压缩方法 uncompress,分别接受源文件路径和目的文件路径。

策略类在命名通常上以 Strategy 为后缀,来指明自身采用策略模式进行设计,以此简化与其他人沟通成本。

public interface CompressStrategy {public boolean compress(String source, String to);public boolean uncompress(String source, String to);
}

再对抽象策略接口进行实现,分别提供zip 压缩算法和 gzip 压缩算法,代码如下:

public class ZipStrategy implements CompressStrategy {@Overridepublic boolean compress(String source, String to) {System.out.println(source + " --> " + to + " ZIP压缩成功!");return true;}@Overridepublic boolean uncompress(String source, String to) {System.out.println(source + " --> " + to + " ZIP解压缩成功!");return true;}
}public class GzipStrategy implements CompressStrategy {@Overridepublic boolean compress(String source, String to) {System.out.println(source + " --> " + to + " GZIP压缩成功!");return true;}@Overridepublic boolean uncompress(String source, String to) {System.out.println(source + " --> " + to + " GZIP解压缩成功!");return true;}
}

代码示例里的实现为了简化只是简单打印操作,具体实现可以参考 JDK API 进行操作。

接下来看下 Context 角色的代码实现:

public class CompressContext {private CompressStrategy compressStrategy;public CompressContext(CompressStrategy compressStrategy) {this.compressStrategy = compressStrategy;}public boolean compress(String source, String to) {return compressStrategy.compress(source, to);}public boolean uncompress(String source, String to) {return compressStrategy.uncompress(source, to);}
}

十分简单,只是传入一个具体算法,然后执行,到这里标准的策略模式就编写完毕了。客户端类只是根据需要指定的具体压缩策略对象传给 CompressContext 对象即可。如果要新增一个压缩算法,也只需对 CompressStrategy 接口提供新的实现即可传给 CompressContext 对象使用。

public class Client {public static void main(String[] args) {CompressContext context;System.out.println("========执行算法========");context = new CompressContext(new ZipStrategy());context.compress("c:\\file", "d:\\file.zip");context.uncompress("c:\\file.zip", "d:\\file");System.out.println("========切换算法========");context = new CompressContext(new GzipStrategy());context.compress("c:\\file", "d:\\file.gzip");context.uncompress("c:\\file.gzip", "d:\\file");}
}

上面的策略模式的应用示例是不是很简单,类似应用也有很多,比如要对接第三方支付,不同的支付平台有不同的支付API,这个API操作都可以抽象成策略接口,客户端发起特定平台的支付接口时,我们只需调用具体的支付策略类执行,并且每个支付策略类相互独立,可替换。

适用场景

本节最后简单总结下策略模式的适用场景:

  • 如果一个对象有很多的行为,它们的实现目的相同,而这些行为使用了多重的条件选择语句来实现。

  • 当一个系统需要动态地切换算法,会选择一种算法去执行。

  • 客户端类不需要知道具体算法的实现细节,只要调用并完成所需要求。

Lambda 与 策略模式

JDK 8 之后,利用Lambda可以提供策略模式更加精简的实现,如果策略接口是一个函数接口,那么不需要声明新的类来实现不同策略,直接通过传递Lambda就可实现,并且更加简洁,具体使用方式参见下方代码:

/*** Context 对象*/
public class Validator {private final ValidationStrategy strategy;public Validator(ValidationStrategy v) {this.strategy = v;}public boolean validate(String s) {return strategy.execute(s);}}/*** 策略接口*/
@FunctionalInterface
public interface ValidationStrategy {boolean execute(String s);
}numericValidator = new Validator((String s) -> s.matches("[a-z]+"));
b1 = numericValidator.validate("aaaa"); // true
lowerCaseValidator = new Validator((String s) -> s.matches("\\d+"));
b2 = lowerCaseValidator.validate("bbbb"); // false

结合 Lambda 的策略模式更适合用于处理简单算法操作的场景,如果算法实现复杂过于冗长复杂,还是建议拆分成单个类进行实现。

策略模式的注意点

策略模式使用起来虽然简单,但它的灵活性在许多项目都能见到其身影,在使用时也有需要注意的地方,下面我们就来看下:

  • 策略模式中每个算法都是完整,不可拆分的原子业务,并且多个算法必须是可以相互替换,,而用哪个算法由外部调用者决定。

  • 当如果具体策略类超过4个,需要使用混合模式减少类膨胀和对外暴露的问题,通过其他模式修正:工厂方法模式,代理模式,享元模式

策略模式的优缺点

一个设计模式的引入必存在它合理的地方和不足,最后我们再说说下策略模式的优缺点。

优点

  • 使用策略模式,可以在不修改原有系统的基础上更换算法或行为,可以灵活地增加新的算法或行为,提供了系统的扩展性

  • 策略模式提供了对一类算法进行管理维护。

  • 使用策略模式可以避免使用多重条件判断,由外部模块决定所要执行的策略类。

缺点

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。

  • 会产生很多策略类,使得类的项目增多。

  • MyBatis 中的九种设计模式

  • 星巴克在GitHub中泄漏API密钥

  • 高效程序员的七个习惯

  • HttpClient 连接池设置不当引发的一次雪崩

  • 为什么程序员要了解业务?

号外!

第一次参与CSDN的博客之星竞选

如果我的公众号和博客曾帮助到您

那我就厚着脸皮恳请得到您的支持

点击“阅读原文”投出您宝贵的五票

每日均可支持5票哦!

换个姿势学设计模式:策略模式相关推荐

  1. 一起学设计模式-策略模式

    一.模式介绍 策略模式(Strategy Pattern):定义一系列算法,将每一个算法封装起来,并让它们可以相互替换. 策略模式让算法独立于使用它的客户而变化,也称为政策模式(Policy). 策略 ...

  2. 看漫画学设计模式——策略模式

    小明和小红是同一家公司的程序员,有一次,公司打算开发一个鸭子的游戏,要求该鸭子要具有会叫声.会游泳.不同的外观的功能,并且对于后期是否添加其他功能待定. 这个任务被项目主管安排给了小明,小明苦思冥想后 ...

  3. 关于设计模式——策略模式-Strategy Pattern

    文章目录 1 策略模式 1.1 模拟鸭子 1.2 设计原则 1.3 整合行为 1.4 模拟鸭子代码的代码 1.5 动态设定行为 1.6 重新查看整体 1.7 继承和组合 1.8 总结 1.9 优劣期间 ...

  4. Python设计模式-策略模式

    Python设计模式-策略模式 代码基于3.5.2,代码如下; #coding:utf-8 #策略模式class sendInterface():def send(self,value):raise ...

  5. 换个姿势学数学:二次函数与拆弹部队

    由于公式的输入问题,该系列在思否停更,需要关注后续更新的请到简书专栏. UX004 什么叫做二次函数? 想必大家上学的时候都接触过吧,大概的形式就是:y=ax^2+bx+c(a≠0) 为什么这种东西 ...

  6. [设计模式] ------ 策略模式

    策略模式 它定义了算法家族,分别封装起来,让他们直接可以互相替换,此模式让算法的变化,不会影响到使用算法的客户 其实很简单,可能很多人都用到了,只不过还不知道这就是策略模式而已. 比如定义一个接口A, ...

  7. java 策略模式 促销_java设计模式——策略模式

    一. 定义与类型 定义:针对一组算法,将每一种算法都封装到具有共同接口的独立的类中,从而是它们可以相互替换.策略模式的最大特点是使得算法可以在不影响客户端的情况下发生变化,从而改变不同的功能.当代码中 ...

  8. Springboot 使用设计模式- 策略模式

    前言 直白点,什么场景我们需要使用到设计模式- 策略模式. 在平常的springboot项目里面做CRUD,我们的习惯性基本是 一个mapper,一个service,一个serviceImpl. 但是 ...

  9. 李建忠设计模式——策略模式Strategy

    目录 1.策略模式定义 1.动机 2.模式定义 3.结构 2.实现例子 1.问题描述 2.代码实现 3.要点总结 4.参考 1.策略模式定义 1.动机 软件构建过程中,某些对象使用的算法可能多种多样, ...

最新文章

  1. mysql 去重取出最小值_5000字总结MySQL单表查询,新手看这一篇足够了!
  2. 图像处理的傅里叶变换理解
  3. springboot 页面下载文件 网页下载文件功能 文件放resourcce下面
  4. 带字母的计算机在线使用,ASCII,进位制在线转换工具
  5. fdtd中时间监视器怎么放_利用FDTD软件仿真拓扑光子(六)-单向传播仿真与软件设置...
  6. Android学习之四大组件简单介绍
  7. 突发!程序员!快别闷头学Python了!
  8. 这个春天我能感觉的到
  9. Tensorflow2.0:使用Keras自定义网络实战
  10. 通过WinForm控件创建的WPF控件无法输入的问题
  11. Python3:递归实现输出目录下所有的文件
  12. iOS:下载/创建证书
  13. 一款度盘高速下载工具
  14. http://www.feedsky.com/help_publishwidget.html
  15. 内存高效的可逆 GAN 网络:Reversible GANs for Memory-efficient Image-to-Image Translation
  16. 手机端上传照片压缩功能canvas
  17. 后端做app连续会员包月功能 -- IOS连续订阅 支付宝周期扣款
  18. 许三多:浮躁社会的反义词
  19. 小程序Swiper组件做日历(周历)左右滑动动态修改数据
  20. spring referrence 阅读

热门文章

  1. linux 编译错误 configure: error: C++ compiler cannot create executables
  2. linux shell 查找某字符串 在文件的行数
  3. python3 打印对象的全部属性
  4. linux c 实现try catch异常捕获
  5. openvas进程间通讯api与报文交互
  6. XP远程桌面连接2008提示:远程计算机需要网络级别身份验证,而您的计算机不支持该验证
  7. Android开发--浅谈ExpandableListActivity
  8. CentOS-7.2部署Squid服务
  9. 安装centos系统时,修改默认网卡名
  10. libvmi编译问题