你是不是还在写着大量的if else语句,if else 不仅难以维护不易扩展,而且使代码臃肿不堪,想不想让你的业务代码更加的健壮,更易扩展,那你一定要学一学今天的主角策略模式。学会了策略模式的使用让会你的代码更加优雅。老板看了给你加薪。同事看了对你仰慕。策略模式大家用了都说好。

阅读完本篇文章你将了解到什么是策略模式,策略模式的优缺点,以及策略模式在源码中的应用。

策略模式引入

在软件开发中,我们常常会遇到这样的情况,实现某一个功能有多条途径,每一条途径对应一种算法,此时我们可以使用一种设计模式来实现灵活地选择解决途径,也能够方便地增加新的解决途径。

譬如商场购物场景中,有些商品按原价卖,商场可能为了促销而推出优惠活动,有些商品打九折,有些打八折,有些则是返现10元等。而优惠活动并不影响结算之外的其他过程,只是在结算的时候需要根据优惠方案结算。
再比如不同的人出去旅游出行的交通方式也不同,经济条件好的会选择高铁飞机,而普通人可能会选择绿皮火车。

富豪老王打算去西藏旅游,老王定了豪华酒店,并且定了机票当天直达。而普通人老张也要去西藏旅游,他打算选择乘坐高铁出行。而学生党的我小汪肯定会选择绿皮火车,主要是为了看路边的风景,而不是因为穷。

下面我们用代码来描述一下上诉场景:

public class Travel {private String vehicle;//出行方式private String name;public String getName() {return name;}public Travel(String name) {this.name = name;}public void setName(String name) {this.name = name;}public void setVehicle(String vehicle) {this.vehicle = vehicle;}public String getVehicle() {return vehicle;}public void TravelTool(){if(name.equals("小汪")){setVehicle("绿皮火车");}else if(name.equals("老张")){setVehicle("高铁");}else if(name.equals("老王")){setVehicle("飞机");}System.out.println(name+"选择坐"+getVehicle()+"去西藏旅游");}}public class Test {public static void main(String[] args) {Travel travel1 = new Travel("小汪");Travel travel2 = new Travel("老王");Travel travel3 = new Travel("老张");travel1.TravelTool();travel2.TravelTool();travel3.TravelTool();}
}
小汪选择坐绿皮火车去西藏旅游
老王选择坐飞机去西藏旅游
老张选择坐高铁去西藏旅游

以上代码虽然完成了我们的需求,但是存在以下问题:

Travel类的TravelTool方法非常庞大,它包含各种人的旅行实现代码,在代码中出现了较长的 if…else… 语句,假如日后小汪发达了也想体验一下做飞机去西藏旅游,那就要去修改TravelTool方法。违反了 “开闭原则”,系统的灵活性和可扩展性较差。
算法的复用性差,如果在另一个系统中需要重用某些算法,只能通过对源代码进行复制粘贴来重用,无法单独重用其中的某个或某些算法。

策略模式

策略模式的介绍

  1. 策略模式(Strategy Pattern)中,定义算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户
  2. 这算法体现了几个设计原则,第一、把变化的代码从不变的代码中分离出来;第二、针对接口编程而不是具体类(定义了策略接口);第三、多用组合/聚合,少用继承(客户通过组合方式使用策略)。

策略模式的原理类图

角色分析
Context(环境类):环境类是使用算法的角色,它在解决某个问题(即实现某个方法)时可以采用多种策略。在环境类中维持一个对抽象策略类的引用实例,用于定义所采用的策略。

Strategy(抽象策略类):它为所支持的算法声明了抽象方法,是所有策略类的父类,它可以是抽象类或具体类,也可以是接口。环境类通过抽象策略类中声明的方法在运行时调用具体策略类中实现的算法。

ConcreteStrategy(具体策略类):它实现了在抽象策略类中声明的算法,在运行时,具体策略类将覆盖在环境类中定义的抽象策略类对象,使用一种具体的算法实现某个业务处理。

我们下面用策略模式来改进一下上面旅行的代码例子。

抽象策略类 Discount

public abstract class AbstractTravle {private String vehicle;private String name;public AbstractTravle(String vehicle, String name) {this.vehicle = vehicle;this.name = name;}public String getVehicle() {return vehicle;}public String getName() {return name;}public abstract void TravelTool();
}

ConcreteStrategy(具体策略类)

public class XiaoWang extends AbstractTravle{public XiaoWang(String vehicle, String name) {super(vehicle, name);}@Overridepublic void TravelTool() {System.out.println(getName()+"选择坐"+getVehicle()+"去西藏旅游");}
}
public class LaoWang extends AbstractTravle{public LaoWang(String vehicle, String name) {super(vehicle, name);}@Overridepublic void TravelTool() {System.out.println(getName()+"选择坐"+getVehicle()+"去西藏旅游");}
}
public class LaoZhang extends AbstractTravle{public LaoZhang(String vehicle, String name) {super(vehicle, name);}@Overridepublic void TravelTool() {System.out.println(getName()+"选择坐"+getVehicle()+"去西藏旅游");}}

环境类

public class Context {private AbstractTravle abstractTravle;public Context(AbstractTravle abstractTravle) {this.abstractTravle = abstractTravle;}public void TravelTool() {System.out.println(abstractTravle.getName()+"选择坐"+abstractTravle.getVehicle()+"去西藏旅游");}
}
public class Test {public static void main(String[] args) {Context context1 = new Context(new LaoWang("飞机", "老王"));context1.TravelTool();Context context2 = new Context(new LaoZang("高铁", "老张"));context2.TravelTool();Context context3 = new Context(new XiaoWang("绿皮火车", "小汪"));context3.TravelTool();}
}
老王选择坐飞机去西藏旅游
老张选择坐高铁去西藏旅游
小汪选择坐绿皮火车去西藏旅游

策略模式总结

策略模式的主要优点如下:

  1. 策略模式提供了对 “开闭原则” 的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。

  2. 策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族,恰当使用继承可以把公共的代码移到抽象策略类中,从而避免重复的代码。

  3. 策略模式提供了一种可以替换继承关系的办法。如果不使用策略模式而是通过继承,这样算法的使用就
    和算法本身混在一起,不符合 “单一职责原则”,而且使用继承无法实现算法或行为在程序运行时的动态切
    换。

  4. 使用策略模式可以避免多重条件选择语句。多重条件选择语句是硬编码,不易维护。

  5. 策略模式提供了一种算法的复用机制,由于将算法单独提取出来封装在策略类中,因此不同的环境类可以方便地复用这些策略类。

策略模式的主要缺点如下:

  1. 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。

  2. 策略模式将造成系统产生很多具体策略类,任何细小的变化都将导致系统要增加一个新的具体策略类。

  3. 无法同时在客户端使用多个策略类,也就是说,在使用策略模式时,客户端每次只能使用一个策略类,不支持使用一个策略类完成部分功能后再使用另一个策略类来完成剩余功能的情况。

适用场景

  1. 一个系统需要动态地在几种算法中选择一种,那么可以将这些算法封装到一个个的具体算法类中,而这些具体算法类都是一个抽象算法类的子类。换言之,这些具体算法类均有统一的接口,根据 “里氏代换原则” 和面向对象的多态性,客户端可以选择使用任何一个具体算法类,并只需要维持一个数据类型是抽象算法类的对象。

  2. 一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重条件选择语句来实现。此时,使用策略模式,把这些行为转移到相应的具体策略类里面,就可以避免使用难以维护的多重条件选择语句。

  3. 不希望客户端知道复杂的、与算法相关的数据结构,在具体策略类中封装算法与相关的数据结构,可以提高算法的保密性与安全性。

源码分析策略模式的典型应用

Java Comparator 中的策略模式

java.util.Comparator 接口是比较器接口,可以通过 Collections.sort(List,Comparator) 和 Arrays.sort(Object[],Comparator) 对集合和数据进行排序,下面为示例程序

@Data
@AllArgsConstructor
public class Student {private Integer id;private String name;@Overridepublic String toString() {return "{id=" + id + ", name='" + name + "'}";}
}
// 降序
public class DescSortor implements Comparator<Student> {@Overridepublic int compare(Student o1, Student o2) {return o2.getId() - o1.getId();}
}// 升序
public class AscSortor implements Comparator<Student> {@Overridepublic int compare(Student o1, Student o2) {return o1.getId() - o2.getId();}
}

通过 Arrays.sort() 对数组进行排序

public class Test1 {public static void main(String[] args) {Student[] students = {new Student(3, "张三"),new Student(1, "李四"),new Student(4, "王五"),new Student(2, "赵六")};toString(students, "排序前");Arrays.sort(students, new AscSortor());toString(students, "升序后");Arrays.sort(students, new DescSortor());toString(students, "降序后");}public static void toString(Student[] students, String desc){for (int i = 0; i < students.length; i++) {System.out.print(desc + ": " +students[i].toString() + ", ");}System.out.println();}
}
排序前: {id=3, name='张三'}, 排序前: {id=1, name='李四'}, 排序前: {id=4, name='王五'}, 排序前: {id=2, name='赵六'},
升序后: {id=1, name='李四'}, 升序后: {id=2, name='赵六'}, 升序后: {id=3, name='张三'}, 升序后: {id=4, name='王五'},
降序后: {id=4, name='王五'}, 降序后: {id=3, name='张三'}, 降序后: {id=2, name='赵六'}, 降序后: {id=1, name='李四'}, 

通过 Collections.sort() 对集合List进行排序

public class Test2 {public static void main(String[] args) {List<Student> students = Arrays.asList(new Student(3, "张三"),new Student(1, "李四"),new Student(4, "王五"),new Student(2, "赵六"));toString(students, "排序前");Collections.sort(students, new AscSortor());toString(students, "升序后");Collections.sort(students, new DescSortor());toString(students, "降序后");}public static void toString(List<Student> students, String desc) {for (Student student : students) {System.out.print(desc + ": " + student.toString() + ", ");}System.out.println();}
}
排序前: {id=3, name='张三'}, 排序前: {id=1, name='李四'}, 排序前: {id=4, name='王五'}, 排序前: {id=2, name='赵六'},
升序后: {id=1, name='李四'}, 升序后: {id=2, name='赵六'}, 升序后: {id=3, name='张三'}, 升序后: {id=4, name='王五'},
降序后: {id=4, name='王五'}, 降序后: {id=3, name='张三'}, 降序后: {id=2, name='赵六'}, 降序后: {id=1, name='李四'}, 

我们向 Collections.sort() 和 Arrays.sort() 分别传入不同的比较器即可实现不同的排序效果(升序或降序)
这里 Comparator 接口充当了抽象策略角色,两个比较器 DescSortor 和 AscSortor 则充当了具体策略角色,Collections 和 Arrays 则是环境角色

Spring Resource 中的策略模式

Spring 把所有能记录信息的载体,如各种类型的文件、二进制流等都称为资源,譬如最常用的Spring配置文件。

在 Sun 所提供的标准 API 里,资源访问通常由 java.NET.URL 和文件 IO 来完成,尤其是当我们需要访问来自网络的资源时,通常会选择 URL 类。

URL 类可以处理一些常规的资源访问问题,但依然不能很好地满足所有底层资源访问的需要,比如,暂时还无法从类加载路径、或相对于 ServletContext 的路径来访问资源,虽然 Java 允许使用特定的 URL 前缀注册新的处理类(例如已有的 http: 前缀的处理类),但是这样做通常比较复杂,而且 URL 接口还缺少一些有用的功能,比如检查所指向的资源是否存在等。

Spring 改进了 Java 资源访问的策略,Spring 为资源访问提供了一个 Resource 接口,该接口提供了更强的资源访问能力,Spring 框架本身大量使用了 Resource 接口来访问底层资源。

public interface Resource extends InputStreamSource {boolean exists();    // 返回 Resource 所指向的资源是否存在boolean isReadable();   // 资源内容是否可读boolean isOpen();   // 返回资源文件是否打开URL getURL() throws IOException;URI getURI() throws IOException;File getFile() throws IOException;  // 返回资源对应的 File 对象long contentLength() throws IOException;long lastModified() throws IOException;Resource createRelative(String var1) throws IOException;String getFilename();String getDescription();    // 返回资源的描述信息
}

Resource 接口是 Spring 资源访问策略的抽象,它本身并不提供任何资源访问实现,具体的资源访问由该接口的实现类完成——每个实现类代表一种资源访问策略。
Spring 为 Resource 接口提供的部分实现类如下:

  1. UrlResource:访问网络资源的实现类。
  2. ClassPathResource:访问类加载路径里资源的实现类。
  3. FileSystemResource:访问文件系统里资源的实现类。
  4. ServletContextResource:访问相对于 ServletContext 路径里的资源的实现类:
  5. InputStreamResource:访问输入流资源的实现类。
  6. ByteArrayResource:访问字节数组资源的实现类。
  7. WritableResource:写资源文件

类图如下:


可以看到 AbstractResource 资源抽象类实现了 Resource 接口,为子类通用的操作提供了具体实现,非通用的操作留给子类实现,所以这里也应用了模板方法模式。(只不过缺少了模板方法)

Resource 不仅可在 Spring 的项目中使用,也可直接作为资源访问的工具类使用。意思是说:即使不使用 Spring 框架,也可以使用 Resource 作为工具类,用来代替 URL。

譬如我们可以使用 UrlResource 访问网络资源。

也可以通过其它协议访问资源,file: 用于访问文件系统;http: 用于通过 HTTP 协议访问资源;ftp: 用于通过 FTP 协议访问资源等

public class Test {public static void main(String[] args) throws IOException {UrlResource ur = new UrlResource("http://image.laijianfeng.org/hello.txt");System.out.println("文件名:" + ur.getFilename());System.out.println("网络文件URL:" + ur.getURL());System.out.println("是否存在:" + ur.exists());System.out.println("是否可读:" + ur.isReadable());System.out.println("文件长度:" + ur.contentLength());System.out.println("\n--------文件内容----------\n");byte[] bytes = new byte[47];ur.getInputStream().read(bytes);System.out.println(new String(bytes));}
}
文件名:hello.txt
网络文件URL:http://image.laijianfeng.org/hello.txt
是否存在:true
是否可读:true
文件长度:47--------文件内容----------hello world!
welcome to http://laijianfeng.org

Spring Bean 实例化中的策略模式

Spring实例化Bean有三种方式:构造器实例化、静态工厂实例化、实例工厂实例化

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="person" class="com.demo.Person"></bean><bean id="personWithParam" class="com.demo.Person"><constructor-arg name="name" value="小旋锋"/></bean><bean id="personWirhParams" class="com.demo.Person"><constructor-arg name="name" value="小旋锋"/><constructor-arg name="age" value="22"/></bean>
</beans>

具体实例化Bean的过程中,Spring中角色分工很明确,创建对象的时候先通过 ConstructorResolver 找到对应的实例化方法和参数,再通过实例化策略 InstantiationStrategy 进行实例化,根据创建对象的三个分支( 工厂方法、有参构造方法、无参构造方法 ), InstantiationStrategy 提供了三个接口方法:

public interface InstantiationStrategy {// 默认构造方法Object instantiate(RootBeanDefinition beanDefinition, String beanName, BeanFactory owner) throws BeansException;// 指定构造方法Object instantiate(RootBeanDefinition beanDefinition, String beanName, BeanFactory owner, Constructor<?> ctor,Object[] args) throws BeansException;// 指定工厂方法Object instantiate(RootBeanDefinition beanDefinition, String beanName, BeanFactory owner, Object factoryBean,Method factoryMethod, Object[] args) throws BeansException;
}

InstantiationStrategy 为实例化策略接口,扮演抽象策略角色,有两种具体策略类,分别为 SimpleInstantiationStrategy 和 CglibSubclassingInstantiationStrategy

在 SimpleInstantiationStrategy 中对这三个方法做了简单实现,如果工厂方法实例化直接用反射创建对象,如果是构造方法实例化的则判断是否有 MethodOverrides,如果有无 MethodOverrides 也是直接用反射,如果有 MethodOverrides 就需要用 cglib 实例化对象,SimpleInstantiationStrategy 把通过 cglib 实例化的任务交给了它的子类 CglibSubclassingInstantiationStrategy。

本文参考了小旋风的策略模式
原文链接:https://blog.csdn.net/wwwdc1012/article/details/83152856

if else终结者——策略模式相关推荐

  1. 【Design pattern】简单工厂过渡策略模式

    把自己当做小菜来跟学<大话设计模式>,跟着故事的思路来走 简单工厂模式:实现一个计算器代码 策略模式:商场打折代码 根据大鸟和小菜的故事,一步步的完善问题的过程!

  2. 设计模式 之美 -- 策略模式

    策略模式作为行为型设计模式中的一种,主要封装相同功能的不同实现算法,用于在用户程序内部灵活切换.对用户来说能够快速替换对应的算法,能够让算法的实现独立于使用的用户. 基本的UML类图如下: 用户使用S ...

  3. 设计模式之策略模式(Strategy)摘录

    23种GOF设计模式一般分为三大类:创建型模式.结构型模式.行为模式. 创建型模式抽象了实例化过程,它们帮助一个系统独立于如何创建.组合和表示它的那些对象.一个类创建型模式使用继承改变被实例化的类,而 ...

  4. Java中的策略模式实例教程

    策略模式是一种行为模式.用于某一个具体的项目有多个可供选择的算法策略,客户端在其运行时根据不同需求决定使用某一具体算法策略. 策略模式也被称作政策模式.实现过程为,首先定义不同的算法策略,然后客户端把 ...

  5. else 策略模式去掉if_设计模式(三)——简单的状态模式代替if-else

    博主将会针对Java面试题写一组文章,包括J2ee,SQL,主流Web框架,中间件等面试过程中面试官经常问的问题,欢迎大家关注.一起学习,一起成长. 前言 大多数开发人员现在还在使用if else的过 ...

  6. C++模式学习------策略模式

    当遇到同一个对象有不同的行为,方法,为管理这些方法可使用策略模式. 策略模式就是对算法进行包装,是把使用算法的责任和算法本身分割开来.通常把一个系列的算法包装到一系列的策略类里面,这些类继承一个抽象的 ...

  7. 模板方法模式与策略模式的区别

    2019独角兽企业重金招聘Python工程师标准>>> 模板方法模式:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中.模板方法使得子类可以在不改变算法结构的情况下,重新定义 ...

  8. 【设计模式】 模式PK:策略模式VS状态模式

    1.概述 行为类设计模式中,状态模式和策略模式是亲兄弟,两者非常相似,我们先看看两者的通用类图,把两者放在一起比较一下. 策略模式(左)和状态模式(右)的通用类图. 两个类图非常相似,都是通过Cont ...

  9. 如何使用 Spring 实现策略模式+工厂模式

    欢迎关注方志朋的博客,回复"666"获面试宝典 一.策略模式 策略模式定义了一组算法,将每个算法都封装起来,并且使它们之间可以互换 1.策略模式主要角色 主要角色如下: 封装角色( ...

  10. 一个支付案例,学会策略模式!

    点击关注公众号,Java干货及时送达 作者 | 叁滴水 来源 | https://blog.csdn.net/qq_30285985/ 前言 在开发时,总会遇到支付的需求,但是支付的方式有很多,支付宝 ...

最新文章

  1. UVa 10112 - Myacm Triangles
  2. 简单的批处理命令(一)
  3. 怎样用Beyond Compare比较两个txt文件
  4. 定时任务scheduleAtFixedRate设定每天某个时刻执行
  5. java-HashMap源码学习
  6. JBPM学习(一):实现一个简单的工作流例子全过程
  7. 数据库系统 - 范式
  8. python不能安装怎么办_python3安装不上怎么办
  9. golang搭建微服务遇到的问题(不断更新)
  10. Python基础——使用with结构打开多个文件
  11. Teststand 中用labview 读写station options属性
  12. 云计算概念简述(讲解)
  13. Decoupled Sparial-Temporal Attention Network forSkeleton-Based Action Recognition
  14. mysql大写和小写_MySQL大写和小写问题
  15. Redis - 几款可视化工具
  16. 指向指针的指针!!(能让初学者绕晕的东西)
  17. 【Web安全基础】PHP基础
  18. HiMobileCam SDK安装使用说明(Hi3559V200)(海思)
  19. Trait 是什么?
  20. Android之侧滑删除RecyclerView

热门文章

  1. html5版本过低,你的浏览器版本过低【解决步骤】
  2. mac 修改vmware的NAT网关
  3. navicat15 安装
  4. python实现音乐播放器_python实现音乐播放器
  5. Lavas的简单入门
  6. 专家称摩尔定律将于2022年失效
  7. 临床阅片有新招!华为与维卓致远发布三维影像阅片系统
  8. 二级计算机excel以宏保存,excel宏保存 设置宏保存位置的操作方法
  9. 解决报错:错误使用 xlsread未找到工作表 ‘sheet1‘
  10. 汇编语言跳转指令总结