设计模式(十一)之行为型模式--命令模式

一、命令模式

1、案例分析

1.1 智能生活项目需求

(1)买一套智能家电,有照明灯、风扇、冰箱、洗衣机,我们只要在手机上安装app就可以控制这些家电工作

(2)这些智能家电来自不同的厂家,我们不像针对每一种家电都安装一个app,分别控制,只希望有一个app就可以控制全部智能家电

(3)要实现一个app控制所有智能家电的需求,则每个智能家电厂家都要提供一个统一的接口给app调用,这时 就可以考虑命令模式

(4)命令模式可将“动作的请求者”从“动作的执行者”对象中解耦出来

(5)在我们的例子中,动作的请求者是手机app,动作的执行者是每个厂商的一个家电产品

2、命令模式基本介绍

(1)命令模式(Command Pattern):在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个。我们只需要在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来设计。

​ 将军与士兵的例子,比如一个将军有100个士兵,将军发起进攻命令,不需要指定每一个确定的士兵去执行操作,只需要有一个助手,让助手去将这些命令传到给每一个士兵即可。

(2)命令模式使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦。

(3)在命令模式中,会将一个请求封装为一个对象,以便使用不同参数来表示不同的请求(即命名),同时命令模式也支持可撤销的操作。

(4)通俗易懂的理解,将军发布命令,士兵去执行。其中有几个角色

​ a.将军(命令发布者)

​ b.士兵(命令的具体执行者)

​ c.命令(连接将军和士兵)

​ Inovke是调用者(将军),Receiver是被调用者(士兵),MyCommand是命令,实现了Command接口,聚合接收对象

3、原理类图

img

(1)Invoke是调用者角色

(2)Command是命令角色,需要执行的所有命令都在这里,可以是接口或抽象类

(3)Receiver是接收者角色,知道如何实施和执行一个请求相关的操作

(4)ConcreteCommand:将一个接收者对象与一个动作绑定,调用接收者相应的操作,实现execute

4、命令模式解决智能生活项目

4.1 思路分析和图解

下图中的撤销是对上一个命令的撤销

img

4.2 类图

img

5、代码实例

5.1 Command接口

1
2
3
4
5
6
7
8
9
//创建命令接口
public interface Command {

//执行动作(操作)
public void execute();

//撤销动作(操作)
public void undo();
}

5.2 LightReceiver类

1
2
3
4
5
6
7
8
9
10
public class LightReceiver {

public void on(){
System.out.println(" 电灯打开了... ");
}

public void off(){
System.out.println(" 电灯关闭了... ");
}
}

5.3 LightOnCommand类

实现Command接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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();
}
}

5.4 LightOffCommand类

实现Command接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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();
}
}

5.5 NoCommand类

实现Command接口

1
2
3
4
5
6
7
8
9
10
11
//没有任何命令,即空执行:用于初始化每个按钮,当调用空命令时,对象什么都不做
//其实,这也是一种设计模式,可以省掉对空的判断
public class NoCommand implements Command {
@Override
public void execute() {
}

@Override
public void undo() {
}
}

5.6 RemoteController类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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();
}
}

5.7 Client类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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();
}
}

结果:

1
2
3
4
5
6
------按下灯的开按钮-------
电灯打开了...
------按下灯的关按钮-------
电灯关闭了...
------按下撤销按钮-------
电灯打开了...

5.8 添加电视机

5.8.1 TVReceiver类

1
2
3
4
5
6
7
8
9
public class TVReceiver {
public void on(){
System.out.println(" 电视机打开了... ");
}

public void off(){
System.out.println(" 电视机关闭了... ");
}
}

5.8.2 TVOnCommand类

实现Command接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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();
}
}

5.8.3 TVOffCommand类

实现Command接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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();
}
}

5.8.4 Client类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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();
}
}

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
------按下灯的开按钮-------
电灯打开了...
------按下灯的关按钮-------
电灯关闭了...
------按下撤销按钮-------
电灯打开了...
==========使用遥控器操作电视机=============
------按下电视机的开按钮---------
电视机打开了...
------按下电视机的关按钮---------
电视机关闭了...
-------按下电视机的撤销按钮-------------
电视机打开了...

6、命令模式在Spring框架Jdbc Template应用的源码分析

6.1 JdbcTemplate类

(1)查看JdbcTemplate类

1
public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {}

(2)查看query方法(点击JdbcTemplate,ctrl+F12出来右边选项)

img

(3)首先进入query方法

1
2
3
4
@Override
public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {
return result(query(sql, new RowMapperResultSetExtractor<>(rowMapper)));
}

(4)然后进去query(sql, new RowMapperResultSetExtractor<>(rowMapper))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@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);
}

(5)里面有一个内部类

1
class QueryStatementCallback implements StatementCallback<T>, SqlProvider

(6)进StatementCallback查看

1
2
3
4
5
@FunctionalInterface
public interface StatementCallback<T> {
@Nullable
T doInStatement(Statement stmt) throws SQLException, DataAccessException;
}

(7)查看QueryStatementCallback类 实现StatementCallback

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@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

img

(8)查看QueryStatementCallback类 实现StatementCallback

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@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类中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@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;
}

6.2 角色分析

(1)StatementCallback接口,类似命令接口(Command)

(2)class QueryStatementCallback implements StatementCallback, SqlProvider,匿名内部类,实现了命令接口,同时也充当命令接收者

class QueryStatementCallback implements StatementCallback, SqlProvider

(3)命令调用者是JdbcTemplate,其中execute(StatementCallback action, boolean closeResources)方法中,调用action.doInStatement方法,不同的实现StatementCallback接口的对象,对应不同的doInStatement实现逻辑

(4)另外,实现StatementCallback接口的子类还有

img

7、命令模式的注意事项和细节

(1)将发起请求的对象与执行请求的对象解耦。发起请求的对象是调用者,调用者只要调用命令对象的execute()方法就可以让接收者工作,而不必知道具体的接收者对象是谁,是如何实现的,命令对象会负责让接收者执行请求的动作,也就是说“请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到了纽带桥梁的作用。

(2)容易设计一个命令队列。只要把命令对象放到队列,就可以多线程的执行命令

(3)容易实现对请求的撤销和重做

(4)不足之处:可能导致某些系统有过多的具体命令类,增加了系统的复杂度,尤其需要注意

(5)空命令也是一种设计模式,它为我们省去了判空的操作。在上面的实例中,如果没有空命令,我们每按一下一个案件都要判空,这给编码带来一定的麻烦

(6)命令模式经典的应用场景:界面的一个按钮都是一条命令,模拟CMD(DOS命令)、订单的撤销/恢复、触发-反馈机制

0%