您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
浅谈设计模式之命令模式
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
浅谈设计模式之命令模式
自猿其说Tech
2021-09-18
IP归属:未知
29320浏览
计算机编程
### 1 命令模式简介 #### 1.1 什么是命令模式 在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个。我们只需在程序运行时指定具体的请求接收者即可,此时可以使用命令模式来设计。 以将军和士兵的为例,假定将军有100个士兵,但他发起进攻命令的时候,不需要指定每一个确定的士兵去执行操作,只需有一个助手就可以去将命令传到给每个士兵。因此我们知道: 1. 命令模式使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦。 1. 在命令模式中,会将一个请求封装为一个对象,以便使用不同参数来表示不同的请求(即命名),同时命令模式也支持可撤销的操作。 1. 通俗易懂的理解,将军发布命令,士兵去执行。其中有几个角色 - - 将军(命令发布者) - - 士兵(命令的具体执行者) - - 命令(连接将军和士兵) #### 1.2 原理类图 分析下类图的信息: ![](//img1.jcloudcs.com/developer.jdcloud.com/f64589f5-93d6-471e-aa47-294661ea45ca20210918142123.png) 1. Invoke是调用者角色(将军),也即是举例中的将军; 1. Command是命令角色(命令),需要执行的所有命令都在这里,可以是接口或抽象类 1. Receiver是接收者角色(士兵),知道如何实施和执行一个请求相关的操作 1. ConcreteCommand:将一个接收者对象与一个动作绑定,调用接收者相应的操作,实现execute ### 2 案列分析 #### 2.1 需求分析 我们需要一套完整的智能家具系统,包含有照明灯、风扇和冰箱等设备,只需一个手机app就可以操纵所有设备工作。但是这些智能家电可能会来自不同厂家,而客户并不希望安装很多个app分别控制,只希望有一个app就可以搞定一切。 要实现一个app控制所有智能家电的需求,则每个智能家电厂家都要提供一个统一的接口给app调用,这时就可以考虑命令模式。我们可以将“动作的请求者”从“动作的执行者”对象中解耦出来,而在我这个例子中,动作的请求者就是手机app,动作的执行者是每个厂商的一个家电产品。 #### 2.2 思路分析和图解 注意:下图中的撤销是对上一个命令的撤销 ![](//img1.jcloudcs.com/developer.jdcloud.com/81590565-72da-4033-80b6-f7194dbe3bc620210918142603.png) ##### 2.3 类图 ![](//img1.jcloudcs.com/developer.jdcloud.com/d296a4fa-923f-4db7-878d-e07499d8218620210918143304.png) #### 2.4 代码示例 #### 2.4.1 基础类 - Command接口 ```java //创建命令接口 public interface Command { //执行动作(操作) public void execute(); //撤销动作(操作) public void undo(); } ``` - LightReceiver类 ```java public class LightReceiver { public void on(){ System.out.println(" 电灯打开了... "); } public void off(){ System.out.println(" 电灯关闭了... "); } } ``` - LightOnCommand类 实现Command接口 ```java public class LightOnCommand implements Command { //聚合LightReceiver LightReceiver lightReceiver; public LightOnCommand(LightReceiver lightReceiver) { this.lightReceiver = lightReceiver; } @Override public void execute() { //调用接收者的方法 lightReceiver.on(); } @Override public void undo() { //调用接收者的方法 lightReceiver.off(); } } ``` - LightOffCommand类 实现Command接口 ```java public class LightOffCommand implements Command{ //聚合LightReceiver LightReceiver lightReceiver; public LightOffCommand(LightReceiver lightReceiver) { this.lightReceiver = lightReceiver; } @Override public void execute() { //调用接收者的方法 lightReceiver.off(); } @Override public void undo() { //调用接收者的方法 lightReceiver.on(); } } ``` - NoCommand类 实现Command接口 ```java //没有任何命令,即空执行:用于初始化每个按钮,当调用空命令时,对象什么都不做 //其实,这也是一种设计模式,可以省掉对空的判断 public class NoCommand implements Command { @Override public void execute() { } @Override public void undo() { } } ``` - RemoteController类 ```java public class RemoteController { //开 按钮的命令数组 Command[] onCommands; Command[] offCommands; //执行撤销的命令 Command undoCommand; //构造器,完成对按钮初始化 public RemoteController(){ onCommands = new Command[5]; offCommands = new Command[5]; for(int i = 0 ;i < 5; i++){ onCommands[i] = new NoCommand(); offCommands[i] = new NoCommand(); } } //给我们的按钮设置你需要的命令 public void setCommand(int no, Command onCommand, Command offCommand){ onCommands[no] = onCommand; offCommands[no] = offCommand; } //按下开按钮 public void onButtonWasPushed(int no){ //找到你按下的开的按钮,并调用对应方法 onCommands[no].execute(); //记录这次的操作,用于撤销 undoCommand = onCommands[no]; } //按下关按钮 public void offButtonWasPushed(int no){ //找到你按下的关的按钮,并调用对应方法 offCommands[no].execute(); //记录这次的操作,用于撤销 undoCommand = offCommands[no]; } //按下撤销按钮 public void undoButtonWasPushed(){ undoCommand.undo(); } } ``` - 服务执行类 ```java public class Client { public static void main(String[] args) { //使用命令设计模式,完成通过遥控器,对电灯的操作 //创建电灯的对象(接受者) LightReceiver lightReceiver = new LightReceiver(); //创建电灯相关的开关命令 LightOnCommand lightOnCommand = new LightOnCommand(lightReceiver); LightOffCommand lightOffCommand = new LightOffCommand(lightReceiver); //需要一个遥控器 RemoteController remoteController = new RemoteController(); //给我们的遥控器设置命令,比如 no = 0是电灯的开和关的操作 remoteController.setCommand(0,lightOnCommand,lightOffCommand); System.out.println("------按下灯的开按钮-------"); remoteController.onButtonWasPushed(0); System.out.println("------按下灯的关按钮-------"); remoteController.offButtonWasPushed(0); System.out.println("------按下撤销按钮-------"); remoteController.undoButtonWasPushed(); } } ``` **结果结果** ``` ------按下灯的开按钮------- 电灯打开了... ------按下灯的关按钮------- 电灯关闭了... ------按下撤销按钮------- 电灯打开了... ``` ##### 2.4.2 添加新智能设备 - TVReceiver类 ```java public class TVReceiver { public void on(){ System.out.println(" 电视机打开了... "); } public void off(){ System.out.println(" 电视机关闭了... "); } } ``` - TVOnCommand类 实现Command接口 ```java public class TVOnCommand implements Command { //聚合TVReceiver TVReceiver tvReceiver; public TVOnCommand(TVReceiver tvReceiver) { super(); this.tvReceiver = tvReceiver; } @Override public void execute() { //调用接收者的方法 tvReceiver.on(); } @Override public void undo() { //调用接收者的方法 tvReceiver.off(); } } ``` - TVOffCommand类 实现Command接口 ```java public class TVOffCommand implements Command { //聚合TVReceiver TVReceiver tvReceiver; public TVOffCommand(TVReceiver tvReceiver) { super(); this.tvReceiver = tvReceiver; } @Override public void execute() { //调用接收者的方法 tvReceiver.off(); } @Override public void undo() { //调用接收者的方法 tvReceiver.on(); } } ``` - 服务执行类 ```java public class Client { public static void main(String[] args) { //使用命令设计模式,完成通过遥控器,对电灯的操作 //创建电灯的对象(接受者) LightReceiver lightReceiver = new LightReceiver(); //创建电灯相关的开关命令 LightOnCommand lightOnCommand = new LightOnCommand(lightReceiver); LightOffCommand lightOffCommand = new LightOffCommand(lightReceiver); //需要一个遥控器 RemoteController remoteController = new RemoteController(); //给我们的遥控器设置命令,比如 no = 0是电灯的开和关的操作 remoteController.setCommand(0,lightOnCommand,lightOffCommand); System.out.println("------按下灯的开按钮-------"); remoteController.onButtonWasPushed(0); System.out.println("------按下灯的关按钮-------"); remoteController.offButtonWasPushed(0); System.out.println("------按下撤销按钮-------"); remoteController.undoButtonWasPushed(); System.out.println("==========使用遥控器操作电视机============="); TVReceiver tvReceiver = new TVReceiver(); TVOnCommand tvOnCommand = new TVOnCommand(tvReceiver); TVOffCommand tvOffCommand = new TVOffCommand(tvReceiver); //给我们的遥控器设置命令,比如no = 1 是电视机的开和关的操作 remoteController.setCommand(1,tvOnCommand,tvOffCommand); System.out.println("------按下电视机的开按钮---------"); remoteController.onButtonWasPushed(1); System.out.println("------按下电视机的关按钮---------"); remoteController.offButtonWasPushed(1); System.out.println("-------按下电视机的撤销按钮-------------"); remoteController.undoButtonWasPushed(); } } ``` 结果 ``` ------按下灯的开按钮------- 电灯打开了... ------按下灯的关按钮------- 电灯关闭了... ------按下撤销按钮------- 电灯打开了... ==========使用遥控器操作电视机============= ------按下电视机的开按钮--------- 电视机打开了... ------按下电视机的关按钮--------- 电视机关闭了... -------按下电视机的撤销按钮------------- 电视机打开了... ``` ### 3 命令模式在Spring框架中的源码分析 下面将以spring框架中常用的JdbcTemplate类作为研究对象。 #### 3.1 JdbcTemplate类 - 查看JdbcTemplate类 ```java public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {} ``` - 查看query方法(点击JdbcTemplate,ctrl+F12出来右边选项) ![](//img1.jcloudcs.com/developer.jdcloud.com/51a6579d-c273-4728-8aef-ba567f64562520210918144740.png) - 首先进入query方法 ```java @Override public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException { return result(query(sql, new RowMapperResultSetExtractor<>(rowMapper))); } ``` - 然后进去#query(sql, new RowMapperResultSetExtractor<>(rowMapper)) ```java @Override @Nullable public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException { Assert.notNull(sql, "SQL must not be null"); Assert.notNull(rse, "ResultSetExtractor must not be null"); if (logger.isDebugEnabled()) { logger.debug("Executing SQL query [" + sql + "]"); } /** * Callback to execute the query. */ class QueryStatementCallback implements StatementCallback<T>, SqlProvider { @Override @Nullable public T doInStatement(Statement stmt) throws SQLException { ResultSet rs = null; try { rs = stmt.executeQuery(sql); return rse.extractData(rs); } finally { JdbcUtils.closeResultSet(rs); } } @Override public String getSql() { return sql; } } return execute(new QueryStatementCallback(), true); } ``` - 这里有一个内部类 ```java class QueryStatementCallback implements StatementCallback<T>, SqlProvider ``` - 进StatementCallback查看 ```java @FunctionalInterface public interface StatementCallback<T> { @Nullable T doInStatement(Statement stmt) throws SQLException, DataAccessException; } ``` - 查看QueryStatementCallback类,它实现StatementCallback ```java @Nullable public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException { Assert.notNull(sql, "SQL must not be null"); Assert.notNull(rse, "ResultSetExtractor must not be null"); if (this.logger.isDebugEnabled()) { this.logger.debug("Executing SQL query [" + sql + "]"); } class QueryStatementCallback implements StatementCallback<T>, SqlProvider { QueryStatementCallback() { } @Nullable public T doInStatement(Statement stmt) throws SQLException { ResultSet rs = null; Object var3; try { rs = stmt.executeQuery(sql); var3 = rse.extractData(rs); } finally { JdbcUtils.closeResultSet(rs); } return var3; } public String getSql() { return sql; } } return this.execute(new QueryStatementCallback(), true); } ``` 该query又是在JdbcTemplate中使用的,因此JdbcTemplate可以看成为Invoke ![](//img1.jcloudcs.com/developer.jdcloud.com/dfa87bf8-26d5-4d57-b01c-fcd6c4e9841420210918144938.png) - 查看QueryStatementCallback类,它实现StatementCallback ```java @Nullable public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException { Assert.notNull(sql, "SQL must not be null"); Assert.notNull(rse, "ResultSetExtractor must not be null"); if (this.logger.isDebugEnabled()) { this.logger.debug("Executing SQL query [" + sql + "]"); } class QueryStatementCallback implements StatementCallback<T>, SqlProvider { QueryStatementCallback() { } @Nullable public T doInStatement(Statement stmt) throws SQLException { ResultSet rs = null; Object var3; try { rs = stmt.executeQuery(sql); var3 = rse.extractData(rs); } finally { JdbcUtils.closeResultSet(rs); } return var3; } public String getSql() { return sql; } } return this.execute(new QueryStatementCallback(), true); } ``` execute的具体代码,也是在JdbcTemplate类中 ```java @Nullable private <T> T execute(StatementCallback<T> action, boolean closeResources) throws DataAccessException { Assert.notNull(action, "Callback object must not be null"); Connection con = DataSourceUtils.getConnection(this.obtainDataSource()); Statement stmt = null; Object var12; try { stmt = con.createStatement(); this.applyStatementSettings(stmt); T result = action.doInStatement(stmt); this.handleWarnings(stmt); var12 = result; } catch (SQLException var10) { String sql = getSql(action); JdbcUtils.closeStatement(stmt); stmt = null; DataSourceUtils.releaseConnection(con, this.getDataSource()); con = null; throw this.translateException("StatementCallback", sql, var10); } finally { if (closeResources) { JdbcUtils.closeStatement(stmt); DataSourceUtils.releaseConnection(con, this.getDataSource()); } } return var12; } ``` #### 3.2 角色分析 通过源码的分析,我们可以知道: 1. StatementCallback接口,类似命令接口(Command) 1. class QueryStatementCallback implements StatementCallback, SqlProvider,匿名内部类,实现了命令接口,同时也充当命令接收者 class QueryStatementCallback implements StatementCallback, SqlProvider 1. 命令调用者是JdbcTemplate,其中execute(StatementCallback action, boolean closeResources)方法中,调用action.doInStatement方法,不同的实现StatementCallback接口的对象,对应不同的doInStatement实现逻辑 1. 另外,实现StatementCallback接口的子类还有 ![](//img1.jcloudcs.com/developer.jdcloud.com/3ee4dd56-07cb-4958-8e12-a691127e5e4620210918145100.png) ### 4 总结和思考 1. 命令模式将发起请求的对象与执行请求的对象解耦,其中发起请求的对象是调用者,调用者只要调用命令对象的execute()方法就可以让接收者工作,而不必知道具体的接收者对象是谁和如何实现。命令对象会负责让接收者执行请求的动作,也即是“请求发起者”和“请求执行者”之间的解耦通过命令对象实现,命令对象起到了纽带桥梁的作用; 1. 命令模式也可以设计成命令队列,只需要将命令对象放到队列中,可以实现多线程并发的执行命令的能力; 1. 命令模式也可以很容易的实现对请求的撤销和重做; 1. 当然命令模式也有一些不足之处,它可能导致某些系统有过多的具体命令类,增加了系统的复杂度,这点在开发过程中尤其需要注意; 1. 需要注意的是,空命令也是一种设计模式,它为我们省去了判空的操作。在上面的实例中如果没有空命令,我们每次触发都需要判空,这给开发和调试带来一定的麻烦; 1. 命令模式经典的应用场景:界面按钮命令一一对应,它可以模拟CMD(DOS命令)、订单的撤销/恢复、触发-反馈机制等。 ------------ ###### 自猿其说Tech-JDL京东物流技术发展部 ###### 作者:网规技术部 张孝祥
原创文章,需联系作者,授权转载
上一篇:京东多端全流程交易解决方案阿波罗平台iOS单元测试实践
下一篇:ES缓存配置及优化方向实践
相关文章
Taro小程序跨端开发入门实战
Flutter For Web实践
配运基础数据缓存瘦身实践
自猿其说Tech
文章数
426
阅读量
2149964
作者其他文章
01
深入JDK中的Optional
本文将从Optional所解决的问题开始,逐层解剖,由浅入深,文中会出现Optioanl方法之间的对比,实践,误用情况分析,优缺点等。与大家一起,对这项Java8中的新特性,进行理解和深入。
01
Taro小程序跨端开发入门实战
为了让小程序开发更简单,更高效,我们采用 Taro 作为首选框架,我们将使用 Taro 的实践经验整理了出来,主要内容围绕着什么是 Taro,为什么用 Taro,以及 Taro 如何使用(正确使用的姿势),还有 Taro 背后的一些设计思想来进行展开,让大家能够对 Taro 有个完整的认识。
01
Flutter For Web实践
Flutter For Web 已经发布一年多时间,它的发布意味着我们可以真正地使用一套代码、一套资源部署整个大前端系统(包括:iOS、Android、Web)。渠道研发组经过一段时间的探索,使用Flutter For Web技术开发了移动端可视化编程平台—Flutter乐高,在这里希望和大家分享下使用Flutter For Web实践过程和踩坑实践
01
配运基础数据缓存瘦身实践
在基础数据的常规能力当中,数据的存取是最基础也是最重要的能力,为了整体提高数据的读取能力,缓存技术在基础数据的场景中得到了广泛的使用,下面会重点展示一下配运组近期针对数据缓存做的瘦身实践。
自猿其说Tech
文章数
426
阅读量
2149964
作者其他文章
01
深入JDK中的Optional
01
Taro小程序跨端开发入门实战
01
Flutter For Web实践
01
配运基础数据缓存瘦身实践
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号