命令模式
新房装修的最后几道工序之一是安装开关,通过开关可以控制一些电器的开闭,比如电灯。开关和电器没有直接的关联关系,而是通过电线相连。也就是说一个开关可能通过不同的电线控制不同电器。在设计模式中,可以将这种结构理解成命令模式,将开关视为请求发送者,电器是请求接受者,电线则是命令对象。为了降低耦合度将发送者和接受者解耦,在发送者和接受者之间引入命令对象,将发送者请求封装在命令对象中,在通过命令对象调接受者方法。命令模式是一种对象行为模式,别名为动作模式或事务模式。
在实际应用中,经常需要向某些对象发送请求(调用其中的某个或某些方法),但是并不知道请求的接受者是谁(什么类型),也不知道被请求的操作是哪个(什么方法),又希望设计松耦合,调用灵活,命令模式是一个较好的解决方案。因为命令模式可以将请求发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系
。
命令模式和适配器模式从结构上有点类似,但它和适配器模式不同的是,适配器模式已经明确知道调用者是谁,接收者是谁,为了使二者可以协同合作而引入适配器;命令模式是只知道调用者是谁不知道接受者是谁而引入命令对象。属于是生命周期上的不同。
UML 类图
命令模式的核心在于引入了命令类,请求发送者只需要指定一个命令对象,在通过命令对象调用接受者的处理方法
组件说明:
- Command(抽象命令):抽象类或接口,声明了用于执行请求的execute()等方法,通过这些方法调用接收者的相关操作。
- ConcreteCommand(具体命令):实现了在抽象命令类中声明的方法,它对应具体的接收者对象。在实现 execute() 方法时,将调用接收者对象的相关操作(Action)。
- Invoker(发送者):它通过命令对象来执行请求。一个调用者并不需要在设计时确定其接收者,因此它只与抽象命令类之间存在关联关系。运行时将一个具体命令对象注入其中,调用具体命令对象的execute()方法,从而实现间接调用请求接收者的相关操作。
- Receiver(接收者):实现对请求的业务处理。
示例
// 抽象命令
abstract class Command {
public abstract void execute();
}
// 具体命令
class ConcreteCommand extends Command {
//维持一个对请求接收者对象的引用
private Receiver receiver;
public void execute() {
//调用请求接收者的业务处理方法action()
receiver.action();
}
}
// 接收者
class Receiver {
public void action() {
//具体操作
}
}
// 请求者
class Invoker {
private Command command;
public Invoker(Command command) {
this.command = command;
}
public void setCommand(Command command) {
this.command = command;
}
//业务方法
public void call() {
command.execute();
}
}
命令队列
有时候我们需要将多个请求排队,当一个请求发送者发送一个请求时,将不止一个请求接收者产生响应,这些请求接收者将逐个执行业务方法,完成对请求的处理
。此时,我们可以通过命令队列来实现。
命令队列的实现方法有多种形式,其中最常用、灵活性最好的一种方式是增加一个CommandQueue类,由该类来负责存储多个命令对象
public class CommandQueue {
//定义一个ArrayList来存储命令队列
private ArrayList<Command> commands = new ArrayList<Command>();
public void addCommand(Command command) {
commands.add(command);
}
public void removeCommand(Command command) {
commands.remove(command);
}
//循环调用每一个命令对象的execute()方法
public void execute() {
for (Command command : commands) {
command.execute();
}
}
}
在增加了命令队列类CommandQueue以后,请求发送者类Invoker将针对CommandQueue编程
public class Invoker {
//维持一个CommandQueue对象的引用
private CommandQueue commandQueue;
public Invoker(CommandQueue commandQueue) {
this. commandQueue = commandQueue;
}
public void setCommandQueue(CommandQueue commandQueue) {
this.commandQueue = commandQueue;
}
public void call() {
commandQueue.execute();
}
}
命令队列可以用于设计批处理应用程序,如果请求接收者的接收次序没有严格的先后次序,还可以使用多线程并发调用命令对象的execute()方法,提高程序的执行效率。
撤销操作
在命令模式中,可以通过在命令类中增加一个逆向操作来实现撤销操作。因为没有保存命令对象的历史状态,所以只能实现一步撤销操作。可以通过引入一个命令集合或其他方式来存储每一次操作时命令的状态,从而实现多次撤销操作。除了Undo操作外,还可以采用类似的方式实现恢复(Redo)操作,即恢复所撤销的操作(或称为二次撤销
)。