命令模式

命令模式是一个高内聚的模式 ,将一个请求封装成一个对象, 从而让你使用不同的请求把客户端参数化, 对请求排队或者记录请求日志, 可以提供命令的撤销和恢复功能 。

命令模式通用类图命令模式通用类图

命令模式有三个角色:

  • Receiver接收者角色:执行命令功能的相关操作,具体命令对象业务的真正实现者。
  • Command命令角色:需要执行的所有命令在该角色中声明,拥有执行命令的抽象方法execute()
  • Invoker调用者角色:是请求发送者,通常拥有很多命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者。

实现

通用的Receiver类:

1
2
3
4
5
6
7
8
9
public abstract class Receiver {
public abstract void find();

public abstract void add();

public abstract void delete();

public abstract void update();
}

接收者可以是多个,具体的Receiver类:

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
public class ConcreteReceiver1 extends Receiver{
@Override
public void find() {
}

@Override
public void add() {
}

@Override
public void delete() {
}

@Override
public void update() {
}
}

public class ConcreteReceiver2 extends Receiver{
@Override
public void find() {
}

@Override
public void add() {
}

@Override
public void delete() {
}

@Override
public void update() {
}
}

命令角色是命令模式的核心,抽象的Command类:

1
2
3
public abstract class Command {
public abstract void execute();
}

具体的Command类,可以在实际应用中扩展该命令类,在每个命令类中,通过构造函数定义该命令是针对哪个接收者发出的,定义一个命令接收的主题,这样调用者就仅需要实现命令的传递即可:

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
public class ConcreteCommand1 extends Command {

private Receiver receiver;

public ConcreteCommand1(Receiver receiver) {
this.receiver = receiver;
}

@Override
public void execute() {
this.receiver.find();
this.receiver.add();
this.receiver.delete();
this.receiver.update();
}
}

public class ConcreteCommand2 extends Command {

private Receiver receiver;

public ConcreteCommand2(Receiver receiver) {
this.receiver = receiver;
}

@Override
public void execute() {
this.receiver.find();
this.receiver.add();
this.receiver.delete();
this.receiver.update();
}
}

调用者Invoker类,不管什么命令都要接收、执行:

1
2
3
4
5
6
7
8
9
10
11
public class Invoker {
private Command command;

public void setCommand(Command command) {
this.command = command;
}

public void action() {
this.command.execute();
}
}

场景类:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Client {
public static void main(String[] args) {
//首先声明调用者Invoker
Invoker invoker = new Invoker();
//定义接收者
Receiver receiver = new ConcreteReceiver1();
//定义一个发送给接收者的命令
Command command = new ConcreteCommand1(receiver);
//把命令交给调用者去执行
invoker.setCommand(command);
invoker.action();
}
}

命令模式的Receiver在实际应用中可以被封装掉,从而减少高层模块Client类对低层模块Receiver角色类的依赖关系,提高系统整体的稳定性。

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
public abstract class Command {
//定义一个子类的全局共享变量
protected final Receiver receiver;
//实现类必须定义一个接收者
public Command(Receiver _receiver){
this.receiver = _receiver;
}
public abstract void execute();
}

public class ConcreteCommand1 extends Command {
public ConcreteCommand1() {
super(new ConcreteReceiver1());
}
public ConcreteCommand1(Receiver receiver) {
super(receiver);
}

@Override
public void execute() {
this.receiver.find();
this.receiver.add();
this.receiver.delete();
this.receiver.update();
}
}

优点

类间解耦:调用者角色和接收者角色之间没有任何依赖关系,调用者实现功能时只需要调用Command抽象类的execute方法即可,不需要了解到底是哪个接收者执行。

可扩展性:Command子类可以非常容易地扩展,而调用者Invoker和高层模块Client不产生严重代码耦合。

和其他模式结合会更优秀:命令模式和结合责任链模式,实现命令族解析任务结合模板方法模式,可减少Command子类的膨胀问题。

缺点

Command子类会出现膨胀问题

应用

命令模式在Spring框架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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
private <T> T execute(StatementCallback<T> action, boolean closeResources) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
Connection con = DataSourceUtils.getConnection(obtainDataSource());
Statement stmt = null;
try {
stmt = con.createStatement();
applyStatementSettings(stmt);
T result = action.doInStatement(stmt);
handleWarnings(stmt);
return result;
} catch (SQLException ex) {
String sql = getSql(action);
JdbcUtils.closeStatement(stmt);
stmt = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw translateException("StatementCallback", sql, ex);
} finally {
if (closeResources) {
JdbcUtils.closeStatement(stmt);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
}

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 + "]");
}
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);
}

StatementCallback接口,类似Command命令接口,QueryStatementCallback匿名内部类,实现了命令接口,同时也充当命令接收者;命令调用者是 JdbcTemplate,不同的实现StatementCallback接口的对象,对应不同的doInStatement实现逻辑;

扩展

实现在没有执行或执行后撤回,有两种方法可以解决,一是结合备忘录模式还原最后状态,该方法适合接收者为状态的变更情况,而不适合事件处理;二是通过增加一个新的命令,实现事件的回滚。