简介
为什么要使用命令模式
在软件设计中,经常需要向某些对象发送请求,但并不知道具体是哪个对象接收请求,也不知道请求的操作是什么,只有在程序运行时才知道请求的接受者是哪个对象。这时,就可以使用命令模式来设计。命令模式可以对发送者和接受者进行解耦,发送者和接受者之间没有直接调用关系,使对象之间的调用更加灵活。
什么是命令模式
命令模式(Command Pattern):将一个请求封装为一个对象,从而可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。
别名
动作(Action)模式、事务(Transaction)模式。
类型
对象行为型模式
遵守的原则
迪米特法则、开闭原则
角色
角色
- Command: 抽象命令类
- 抽象类或者接口
- 声明了用于执行请求的execute()等方法,在方法中可以调用接受者的方法。
- ConcreteCommand: 具体命令类
- 具体类
- 实现了抽象命令类的方法。
- 含有请求接受者的引用,可以调用请求接受者的方法。
- Invoker: 调用者
- 具体类
- 请求的发送者
- 通过命令类对象来执行请求。和抽象命令类之间存在关联关系,在程序运行时可以将一个具体命令对象注入其中,再调用具体命令对象的execute()方法,从而实现间接调用请求接收者的相关操作。
- Receiver: 接收者
- 具体类
- 处理请求,实现具体的业务操作。
- Client:客户类
- 客户。创建一个具体命令对象并制定它的接受者。
UML类图
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIn5GcuQmbh1WbvN2LcNnbyVGd0FGUudWazVGRvw1cn9GbC5ERTN0LcJXZ0NXYt9CXz9GdvhGUud3bktmch10LclWaldnbhB3Lc12bj5CduVGdu92YyV2c1JWdoRXan5ydhJ3Lc9CX6MHc0RHaiojIsJye.png)
代码实现
- 新建接收者Receiver.java
- 新建抽象命令类Command.java
- 新建具体命令类ConcreteCommand.java
- 新建调用者Invoker.java
- 新建客户类Client.java
接收者Receiver.java
class Receiver {
public void action() {
// 处理请求,实现具体的业务操作
System.out.println("Receiver.action()");
}
}
抽象命令类Command.java
abstract class Command {
public abstract void execute();
}
具体命令类ConcreteCommand.java
class ConcreteCommand extends Command {
private Receiver receiver; // 维持一个对请求接收者对象的引用
public ConcreteCommand(Receiver receiver) {
super();
this.receiver = receiver;
}
public void execute() {
receiver.action(); // 调用请求接收者的业务处理方法action()
}
}
调用者Invoker.java
class Invoker {
private Command command;
// 构造注入
public Invoker(Command command) {
this.command = command;
}
// 设值注入
public void setCommand(Command command) {
this.command = command;
}
// 业务方法,用于调用命令类的execute()方法
public void call() {
command.execute();
}
}
客户类Client.java
public class Client {
public static void main(String[] args) {
Receiver pReceiver = new Receiver();
ConcreteCommand pCommand = new ConcreteCommand(pReceiver);
Invoker pInvoker = new Invoker(pCommand);
pInvoker.call();
}
}
测试
运行Client.java的main()
Receiver.action()
实例1:电视机遥控器
UML类图
代码实现
- 新建接收者Television.java
- 新建抽象命令类Command.java
- 新建具体命令类OpenTVCommand.java、CloseTVCommand.java
- 新建调用者Invoker.java
- 新建客户类Client.java
接收者Television.java
public class Television {
public void trunOn() {
// 处理请求,实现具体的业务操作
System.out.println("Television.trunOn(),电视打开了");
}
public void trunOff() {
// 处理请求,实现具体的业务操作
System.out.println("Television.trunOff(),电视关闭了");
}
}
抽象命令类Command.java
abstract class Command {
public abstract void execute();
}
具体命令类
OpenTVCommand.java
public class OpenTVCommand extends Command {
private Television television; // 维持一个对请求接收者对象的引用
public OpenTVCommand(Television television) {
super();
this.television = television;
}
public void execute() {
television.trunOn();
}
}
CloseTVCommand.java
public class CloseTVCommand extends Command {
private Television television; // 维持一个对请求接收者对象的引用
public CloseTVCommand(Television television) {
super();
this.television = television;
}
public void execute() {
television.trunOff();
}
}
调用者Invoker.java
class Invoker {
private Command command;
// 构造注入
public Invoker(Command command) {
this.command = command;
}
// 设值注入
public void setCommand(Command command) {
this.command = command;
}
// 业务方法,用于调用命令类的execute()方法
public void call() {
command.execute();
}
}
客户类Client.java
public class Client {
public static void main(String[] args) {
Television television = new Television();
OpenTVCommand openTVCommand = new OpenTVCommand(television);
Invoker invoker = new Invoker(openTVCommand);
invoker.call();
CloseTVCommand closeTVCommand = new CloseTVCommand(television);
invoker = new Invoker(closeTVCommand);
invoker.call();
}
}
测试
运行Client.java的main()
Television.trunOn(),电视打开了
Television.trunOff(),电视关闭了
扩展
宏命令
宏命令又称为组合命令,它是命令模式和组合模式联用的产物。
- 宏命令也是一个具体命令,不过它包含了对其他命令对象的引用,在调用宏命令的execute()方法时,将递归调用它所包含的每个成员命令的execute()方法执行一个宏命令将执行多个具体命令,从而实现对命令的批处理。
- 一个宏命令的成员对象可以是简单命令,还可以继续是宏命令。
命令队列
当一个请求发送者发送一个请求时,可能不止一个请求接收者产生响应,这些请求接收者逐个执行业务方法,完成对请求的处理。这种情况可以通过命令对象来处理。
实现方法
增加一个CommandQueue类,由该类来负责存储多个命令对象,而不同的命令对象可以对应不同的请求接收者。
撤销操作
实现方法
- 在命令类中增加一个逆向操作来实现撤销操作。
- 通过保存对象的历史状态来实现撤销(可使用备忘录模式(Memento Pattern)来实现)。
请求日志
请求日志就是将请求的历史记录保存下来,通常以日志文件(Log File)的形式永久存储在计算机中。
实现方法
- 将命令对象通过序列化写到日志文件中,此时命令类必须实现Java.io.Serializable接口。
组合模式Composite可用来实现宏命令
待补充
备忘录模式可用来实现撤销功能
待补充
优缺点
优点
- 遵守“开闭原则”。加入新的具体命令类不会影响原有系统。
- 遵守“迪米特法则”。请求者和接受者之间不存在直接引用,实现了解耦。
- 可以比较容易地设计一个命令队列和宏命令(组合命令)。
- 可以方便地实现对请求的撤销(Undo)和恢复(Redo)。
缺点
- 系统中可能会有大量的命令类。
适用环境
- 系统需要在不同的时间指定请求、将请求排队和执行请求。
- 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
- 系统需要将一组操作组合在一起,即支持宏命令。
- 支持修改日志。
使用场景
- 待补充
问题
画出宏命令,命令队列,撤销操作,请求日志的UML结构图、并写出实现代码
在软件开发中,你在哪里用到了命令模式?
待补充。