定义
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。
结构和说明
示例代码
public class CommandDemo {
/**
* 命令接口, 申明执行的操作
*/
public static interface Command {
/**
* 执行命令对应的操作
*/
void execute();
}
public static class ConcreteCommand implements Command {
/**
* 持有相应的接收者对象
*/
private Receiver receiver;
/**
* 示意,命令对象可以有自己的状态
*/
private String state;
/**
* 构造方法,传入相应的接收者对象
*
* @param receiver 相应的接收者对象
*/
public ConcreteCommand(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
// 通常会转调接收者对象的相应方法,让接收者来真正执行功能
receiver.action();
}
}
/**
* 接收者对象
*/
public static class Receiver {
/**
* 示意方法,真正执行命令相应的操作
*/
public void action() {
// 真正执行命令操作的功能代码
}
}
/**
* 调用者
*/
public static class Invoker {
/**
* 持有命令对象
*/
@Setter
private Command command;
/**
* 示意方法,要求命令执行请求
*/
public void runCommand() {
// 调用命令对象的执行方法
command.execute();
}
}
/**
* 注意这个不是我们通常意义上的测试客户端,主要功能是要创建
* 命令对象并设定它的接收者,因此这里并没有调用执行的代码
*/
public static class Client {
/**
* 示意,负责创建命令对象,并设定它的接收者
*/
public void assemble() {
// 创建接收者
Receiver receiver = new Receiver();
// 创建命令对象,设定它的接收者
Command command = new ConcreteCommand(receiver);
// 创建 Invoker,把命令对象设置进去
Invoker invoker = new Invoker();
invoker.setCommand(command);
}
}
}
调用顺序
组装过程
执行过程
优缺点
优点
- 更松散的耦合
命令模式使得发起命令的对象-客户端,和具体实现命令的对象-接收者对象完全解耦,也就是说发起命令的对象完全不知道具体实现对象是谁,也不知道如何实现。
- 更动态的控制
命令模式把请求封装起来,可以动态地对它进行参数化、队列化和日志化等操作,从而使得系统更加灵活。
- 很自然的复合命令
命令模式中的命令对象能够很容易地组合成复合命令,也就是前面讲的宏命令,从而使系统操作更简单,功能更强大。
- 更好的扩展性
由于发起命令的对象和具体的实现完全解耦,因此扩展新的命令就很容易,只需要实现新的命令对象,然后在装配的时候,把具体的实现对象配置到命令对象中,然后就可以使用这个命令对象,已有的实现完全不用变化。
思考
本质
封装请求
何时选用
- 如果需要抽象出需要执行的动作,并参数化这些对象,可以选用命令模式。将这些需要执行的动作抽象成为命令,然后实现命令的参数化配置。
- 如果需要在不同的时刻指定、排列和执行请求,可以选用命令模式。将这些请求封装成为命令对象,然后实现将请求队列化。
- 如果需要支持取消操作,可以选用命令模式,通过管理命令对象,能很容易地实现命令的恢复和重做功能。
- 如果需要支持当系统奔溃时,能将系统的操作功能重新执行一边,可以选用命令模式。将这些操作功能的请求封装成命令对象,然后实现日志命令,就可以在系统恢复以后,通过日志获取命令列表,从而重新执行一遍功能。
- 在需要事务的系统中,可以选用命令模式。命令模式提供了对事务进行建模的方法。命令模式有一个别名就是 Transaction。
相关模式
- 命令模式和组合模式
这两个模式可以组合使用
在命令模式中,实现宏命令的功能就可以使用组合模式来实现
- 命令模式和备忘录模式
这两个模式可以组合使用。
在命令模式中,实现可撤销操作功能时,通过保存命令执行前的状态,撤销的时候就把状态恢复。如果采用这种方法实现,就可以考虑使用备忘录模式。
如果状态存储在命令对象中,那么还可以使用原型模式,把命令对象当作原型来克隆一个新的对象,然后将克隆出来的对象通过备忘录模式存放。
- 命令模式和模版方法模式
这两个模式从某种意义上有相似的功能,命令模式可以作为模版方法的一种替代模式,也就是说命令模式可以模仿实现模版方法模式的功能。
如果前面讲述的退化的命令模式可以实现 Java 的回调,而 Invoker 智能化后向服务进化,如果 Invoker 的方法是一个算法骨架,其中有两步在这个骨架里面没有具体实现,需要外部来实现,这个时候就可以通过回调命令接口来实现。而类似的功能在模版方法中,是先调用抽象方法,然后等待子类来实现。
可以看出虽然实现方式不一样,但是可以实现相同的功能。