从java多态到策略模式_设计模式中的多态——策略模式详解

2. 策略模式详解

2.1 策略模式定义

策略模式定义了一系列算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户端而独立的变化。

可以使用多态进行类比来理解策略模式的定义。一系列算法可以理解成接口的不同实现类,因为不同实现类都实现了相同的接口,因而它们也可以相互替换。策略模式让算法独立于客户端而变化与接口的实现类可以独立于使用接口的客户端变化类似。

2.2 策略模式的UML类图

9d758fe783cccad90e5962ac9ce43481.png

从UML类图上可以看出,策略模式中主要有3个角色

抽象策略接口

上图中的Strategy即抽象策略接口,接口中定义了抽象的策略算法algorithm()。

具体的策略实现类

上图中的StrategyA和StrategyB即具体的策略实现。不同的策略实现类都实现了抽象策略接口,并重写了其抽象策略方法。因为都实现了相同的策略接口,因而算法可以相互替换,并且可以动态的改变具体的算法实现。

封装策略的上下文环境

上图中的Context即策略的上下文环境。它屏蔽了高层模块对策略算法的直接访问,封装了可能存在的变化。而且提供了修改Strategy的setter方法,可以动态的改变算法的具体实现。

3.策略模式的优点

我们可以结合使用策略模式的例子并与其它实现方案进行对比来看看策略模式到底有什么好处

3.1 一个使用策略模式的例子

定义一个汽车类Car。由于汽车最大的特点是能跑,因而我们赋予该类一个move行为。但要跑起来需要提供能源,通常而言这种能源是汽油,但现在纯靠电池驱动的汽车也越来越多。因而Car的move行为就有两种不同的行为,一种是使用汽油跑,一种是使用电能跑。因而我们可以这么定义

抽象的汽车类Car

/**

* @author: takumiCX

* @create: 2018-10-13

**/

public abstract class Car {

//汽车品牌

private String brand;

public Car(String brand) {

this.brand = brand;

}

public Car(String brand, MoveStrategy strategy) {

this.brand = brand;

this.moveStrategy=strategy;

}

//汽车的运行策略:使用汽油运行,使用电能运行等等

private MoveStrategy moveStrategy;

//运行方法

public void move() {

System.out.print(brand);

moveStrategy.move();

}

public void setMoveStrategy(MoveStrategy moveStrategy) {

this.moveStrategy = moveStrategy;

}

}

在抽象汽车类中定义了一个move()方法表示汽车具有运行的行为,但是由于到底是使用汽油运行还是使用电能运行并没有直接定义在里面,而是调用了策略接口中定义的move方法。该策略接口以组合的方式封装在Car内部,并提供了setter方法供客户端动态切换汽车的运行方式。

使用汽油运行的策略实现

/**

* @author: takumiCX

* @create: 2018-10-14

**/

/**

* 使用汽油运行的策略实现

*/

public class GasolineMoveStrategy implements MoveStrategy{

@Override

public void move() {

System.out.println(" Use Gasoline Move!");

}

}

使用电池运行的策略实现

/**

* @author: takumiCX

* @create: 2018-10-15

**/

/**

* 使用电能运行的策略实现

*/

public class ElectricityMoveStrategy implements MoveStrategy {

@Override

public void move() {

System.out.println(" Use Electricity Move!");

}

}

具体的汽车实现类

比如我们通过继承的方式定义一辆特斯拉汽车,特斯拉汽车默认是纯电动的

/**

* @author: takumiCX

* @create: 2018-10-13

**/

public class TeslaCar extends Car {

public TeslaCar(String brand) {

super(brand,new ElectricityMoveStrategy());

}

}

客户端代码

首先构造一辆特斯拉车观察其运行方式,并通过setter方法动态改变其运行方式为汽油驱动

/**

* @author: takumiCX

* @create: 2018-10-13

**/

public class Client {

public static void main(String[] args) {

TeslaCar car = new TeslaCar("Tesla");

car.move();

car.setMoveStrategy(new GasolineMoveStrategy());

car.move();

}

}

运行结果

deac226d52c3cdf770c6d43d97b8bfaa.png

3.2 与其他实现方式的对比

其实上面的例子除了使用策略模式外,还有其他实现方式,但它们都有比较明显的缺点。

3.2.1接口的实现方式

/**

* @author: takumiCX

* @create: 2018-10-15

**/

public interface Move {

void move();

}

并让抽象父类Car实现它

/**

* @author: takumiCX

* @create: 2018-10-13

**/

public abstract class Car implements Move{

//汽车品牌

private String brand;

public Car(String brand) {

this.brand = brand;

}

}

这样所有继承Car的具体汽车类都必须实现自己的move方法,也就是让具体的汽车子类来决定汽车的具体行为:到底是使用汽油运行还是使用电池运行。但是这么做至少有以下几个缺点

1.具体的汽车运行行为不方便后期维护。因而move行为无法被复用,具体的实现都分散在了子类中。如果要对某种驱动方式的实现进行修改,不得不修改所有子类,这简直是灾难。

2.导致类数量的膨胀。同样品牌的汽车,由于有汽油和电动两种运行方式,不得不为其维护两个类,如果在增加一种驱动方式,比如氢能源驱动,那不得为每个品牌的汽车再增加一个类。

3.不方便move行为的扩展,也不方便动态的更换其实现方式。

3.2.2 if-else的实现方式

move方法接受客户端传递的参数,通过if-else或者swich-case进行判断,选择正确的驱动方式。

public void move(String moveStrategy) {

if("electricity".equals(moveStrategy)){

System.out.println(" Use Electricity Move!");

}else if("gasoline".equals(moveStrategy)){

System.out.println(" Use Gasoline Move!");

}

}

但这样做相当于硬编码,不符合开闭原则。比如我要增加一种氢能源的驱动方式,这种实现就需要修改move中的代码。而如果使用上面说的策略模式,则只需要增加一个实现实现策略接口的具体策略实现类,而不需要修改move中的任何代码,即可被客户端所使用。

/**

* @author: takumiCX

* @create: 2018-10-15

**/

public class HydrogenMovetrategy implements MoveStrategy {

@Override

public void move() {

System.out.println(" Use Hydrogen Move!");

}

}

3.3 使用策略模式的优点

1.可以优化类结构,当类的某种功能有多种实现时,可以在类中定义策略接口,将真正的功能实现委托给具体的策略实现类。这样避免了类膨胀,也能更好的进行扩展和维护。

2.避免使用多重条件判断导致的硬编码和扩展性差的问题

3.可以使具体的算法实现自由切换,增强程序设计的弹性。

4. 使用工厂方法模式改进原有策略模式

所有的策略实现都需要对外暴露,上层模块必须知道具体的策略实现类,这与迪米特法则相违背。为此,可以使用工厂方法模式进行解耦。

策略工厂接口

/**

* @author: takumiCX

* @create: 2018-10-16

**/

public interface MoveStrategyFactory {

MoveStrategy create();

}

氢能源驱动方式的工厂

/**

* @author: takumiCX

* @create: 2018-10-16

**/

public class HydrogenMoveStrategyFactory implements MoveStrategyFactory {

@Override

public MoveStrategy create() {

return new HydrogenMovetrategy();

}

}

客户端

/**

* @author: takumiCX

* @create: 2018-10-13

**/

public class Client {

public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {

TeslaCar car = new TeslaCar("Tesla");

MoveStrategyFactory factory = new HydrogenMoveStrategyFactory();

MoveStrategy moveStrategy = factory.create();

car.setMoveStrategy(moveStrategy);

car.move();

}

}

这样我们通过工厂方法模式封装了具体策略类的创建过程,同时也避免了向高层模块暴露。最后运行结构如下

3eb907abbc334a879085838385da06a0.png

5. 总结

当完成某项功能有多种不同的实现时,可以实用策略模式。策略模式封装了不同的算法,并且使这些算法可以相互替换,这提高了代码的复用率也增强了程序设计的弹性。并且可以结合其他设计模式比如工厂方法模式向上层模块屏蔽具体的策略类,使代码更易于扩展和维护。

5. 参考资料

《Head First 设计模式》

《设计模式之禅》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/557610.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux服务器安装zookeeper本地项目远程连接

linux服务器安装zookeeper本地项目远程连接 zookeeper linux 服务器安装,本地idea连接 先决条件:一台linux服务器,服务器里面已经安装好java环境(安装Java看这里:https://blog.csdn.net/qq_43842093/article/details…

android 获取蓝牙设备id_【报Bug】安卓平台获取不到蓝牙设备服务列表(ios可以)...

产品分类:uniapp/AppPC开发环境操作系统:WindowsPC开发环境操作系统版本号:win10HBuilderX类型:正式HBuilderX版本号:3.0.7手机系统:Android手机系统版本号:Android 9.0手机厂商:华为…

java gson 工具类_GSON 实体 转换工具类

/*** Gson转换工具类*/public class GsonUtils {/*** param jsonString* json字符串* param cls* 要转换的类* param * 返回要转换的类* return*/public static T getPerson(String jsonString, Class cls) {T t null;try {Gson gson new G…

修改linux远程主机名命令hostname

hostname命令 用这个命令:之后重新登录

java string字符操作_Java对String类型字符串的各种操作姿势

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼//获取字符串的长度String str2 "helloword";System.out.println(str2.length());//利用数组创建string对象char[] cha {h,e,l,l,o,w,o,r,d,!};String str new String(cha);System.out.println(str);//利用数组创建st…

zookeeper下载安装过程

1.1 下载安装 1、环境准备 ZooKeeper服务器是用Java创建的,它运行在JVM之上。需要安装JDK 7或更高版本。 2、上传 将下载的ZooKeeper放到/opt/ZooKeeper目录下 #上传zookeeper altp put f:/setup/apache-zookeeper-3.5.6-bin.tar.gz #打开 opt目录 cd /opt #创…

java 计算反码_java基础知识-原码、反码、补码、运算符

一、原码、反码、补码原码一个数转化成二进制。用最高位来表示正负,最高位为0表示正数,最高位为1表示负数。例如:short i5;因为在java里short占2个字节转化成二进制就是 00000000 00000101所以 00000000 00000101就是5的原码short…

dubbo-admin安装和简单使用

一、dubbo-admin安装 1、环境准备 dubbo-admin 是一个前后端分离的项目。前端使用vue,后端使用springboot,安装 dubbo-admin 其实就是部署该项目。我们将dubbo-admin安装到开发环境上。要保证开发环境有jdk,maven,nodejs 安装n…

java 文件流 重写_java中关于文件流的总结

[File类]1、 作用: 用于对磁盘文件进行操作。 删除、创建等。2、 三种常用的构造函数:① File file1 new File("F:\\test");直接传入一个路径,拿到一个文件或者是文件夹。② File file2 new File("F:\\test","tes…

java接口的默认方法,实现类调用接口默认方法

概述 Java8带来了一些全新的特性,包括lambda表达式、函数接口、方法引用、流、可选方法、接口中的静态方法和默认方法。 在本文中,我们将深入讨论为什么java8接口新增了默认方法,如何使用默认方法,并讨论一些有用的用例。 默认…

java程序的加载顺序_Java类的加载顺序

问题昨天有人问我一个类中有静态方法,有静态代码块,普通代码块,构造函数,普通方法,静态方法,那么它们的加载顺序是什么?如果有之类继承该类,也有如上的方法,那么加载顺序…

java.lang.Thread类详解,yield方法,join方法,interrupt方法,interrupted方法,destroy方法

一、前言 位于java.lang包下的Thread类是非常重要的线程类,它实现了Runnable接口,今天我们来学习一下Thread类,在学习Thread类之前,先介绍与线程相关知识:线程的几种状态、上下文切换,然后接着介绍Thread类…

hashSet与treeSet的去重原理

hashSet与treeSet的去重原理 1、TreeSet去重原理 :compareTo 可以实现排序及去重:如果compareTo返回0,说明是重复的,返回的是自己的某个属性和另一个对象的某个属性的差值,如果是负数,则往前面排&#xff…

php mysql group by_php – 如何在mysql查询中解决“不在GROUP BY中”错误

我有两个模型:帖子和喜欢有一对多的关系(所以,一个帖子有很多喜欢). Likings模型还有一个isActive字段,表示喜欢是主动还是被动.我想获得(排序)前5个帖子,这些帖子已经收到了最大的“活跃”喜欢(只有喜欢其isActive字段为true的帖子才会被考虑).这是查询&#xff1a…

排序实训问答

排序 注意事项: 问:你这排序怎么会出现两个0毫秒 或者怎么会出现0毫秒的现象呢? 答:在计算机中,时间的计算是以毫秒为单位的。当两个时间间隔非常短,小于1ms时,计算机可能会将时间计算为0毫秒…

git stash 缓存 简介

当我们在使用git的时候,又是会有这种情况:当新的需求了的时候。我们需要为此需求新建一个分支,再次分支上进行修改,当经过测试,提交代码时,在将其合并到主分支,或生产分支上。 但是有时候也有失…

java jsp session_JSP中Session的使用

你的session对象ID是:int Num 0; //定义前面要加!号synchronized void countPeople() { //同步方法Num;}%>if (session.isNew()) { //推断是否为新用户countPeople();String str String.valueOf(Num);session.setAttribute("count", str); //将str 加…

java中的线程池有哪些,分别有什么作用?

java中的线程池有哪些,分别有什么作用? 1.进程-线程简单介绍 2.java的线程池是什么,有哪些类型,作用分别是什么 3.使用线程池的优点 1.进程-线程的简单介绍 进程 什么是进程呢? 进程是计算机中的程序关于某数据集合…

二进制编译安装mysql_二进制编译安装mysql

1:解压包及做链接# tar xvf mysql-5.5.13-linux2.6-i686.tar.gz -C /usr/local# cd /usr/local# ln -sv mysql-5.5.13-linux2.6-i686 mysql# cd mysql---------------------------------------------------------------2:增加用户mysql及创建数据库数据存放目录/mydata/data# g…

线程安全的集合类有哪些?

验证ArrayList线程不安全 ArrayList 应当是开发中用到的最多的集合类,是动态列表,List 接口的实现类。 多数情况下,我们实在单线程环境使用,或者是在方法内部,以局部变量的形式使用,一般不会出现线程安全问…