前言
命令模式是常用的行为型设计模式之一,它将请求发送者与请求接收者解耦,请求发送者通过命令对象来间接引用接收者,使得系统具有更好的灵活性,可以在不修改现有系统源代码的情况下让相同的发送者对应不同的接收者。
正文
命令模式可以将请求发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。
一、定义
定义如下:
命令模式: 将一个请求封装为一个对象,从而可用不同的请求对客户进行参数化,对请求排队或者记录请求日志,以及支持可撤销的操作。
Command Pattern: Encapsulate a request as an object,thereby letting you parameterize clients with
different requests,queue or log requests,and support undoable operations.
命令模式是一种对象行为型模式,其别名为动作(Action)模式或事务(Transaction)模式。
二、结构
(1)Command(抽象命令类):抽象命令类一般是一个抽象类或接口,在其中声明了用于执行请求的execute()等方法,通过这些方法可以调用请求接收者的相关操作。
(2)ConcreteCommand(具体命令类):具体命令类是抽象命令类的子类,实现了在抽象命令类中声明的方法,它对应具体的接收者对象,将接收者对象的动作绑定其中。具体命令类在实现excute()方法时将调用接收者对象的相关操作(Action)。
(3)Invoker(调用者):调用者即请求发送者,它通过命令对象来执行请求。一个调用者并不需要在设计时确定其接收者,因此它只与抽象命令类之间存在关联关系。在程序运行时可以将一个具体命令对象注入其中,再调用具体命令对象的execute()方法,从而实现间接调用请求接收者的相关操作。
(4)Receiver(接收者):接收者执行与请求相关的操作,具体实现对请求的业务处理。
三、实现
典型的抽象命令类代码如下:
public abstract class Command{
puublic abstract void execute();
}
请求发送者典型代码如下:
public 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();
}
}
具体命令类,典型代码如下:
public class ConcreteCommand extends Command{
private Receiver receiver; //维持一个对请求接收者对象的引用
public void execute(){
receiver.action(); //调用请求接收者的业务处理方法action()
}
}
请求接收者,典型代码如下:
public class Receiver{
public void action(){
//具体操作
}
}
四、实现命令队列
有时候,当一个请求发送者发送一个请求时有不止一个请求接收者产生响应,这些请求接收者将逐个执行业务方法,完成对请求的处理,此时可以通过命令队列来实现。
命令队列的实现方法有多种,其中最常用、灵活性最好的一种方式是增加一个CommandQueue类,典型代码如下:
public calss CommandQueue{
// 定义一个ArrarList来存储命令队列
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(Object command: commands){
((Command)command).execute();
}
]
}
请求发送者Invoker代码:
public class Invoker{
private CommandQueue commandQueue; //维持一个CommandQueue对象的引用
//构造注入
public Invoker(CommandQueue commandQueue){
this.commandQueue = commandQueue;
}
//设值注入
public void setCommandQueue(CommandQueue commandQueue){
this.commandQueue = commandQueue;
}
//调用CommandQueue类的execute()方法
public void call(){
commandQueue.execute();
}
}
五、记录请求日志
请求日志就是将请求的历史记录保存下来,通常以日志文件(Log File)的文件形式永久存储在计算机中。请求日志常用功能如下:
(1)一旦系统发生故障,日志文件可以为系统提供一种恢复机制,在请求日志文件中可以记录用户对系统的每一步操作,从而让系统能够顺利恢复到某一个特定的状态。
(2)请求日志也可以用于实现批处理,在一个请求日志文件中可以存储一系列命令对象,如一个命令队列。
(3)可以将命令队列中的所有命令对象都存储在一个日志文件中,每执行一个命令则从日志文件中删除一个对应的命令对象防止因为断电或者系统重启等原因造成请求丢失,而且可以避免重新发送全部请求时造成某些命令的重复执行。
六、宏命令
宏命令(Macro Command)又称为组合命令(Composite Command),它是组合模式和命令模式联用的产物。宏命令是一个具体命令类,它拥有一个集合,在该集合中包含了对其他命令对象的引用。通常宏命令不直接与请求接收者交互,而是通过它的成员来调用接收者的方法。当调用宏命令的execute()方法时将递归调用它所包含的每个成员命令的execute()方法,一个宏命令的成员可以是简单命令,也可以继续是宏命令。执行一个宏命令将触发多个具体命令的执行,从而实现对命令的批处理。
七、命令模式优缺点与适用环境
命令模式是一种使用频率非常高的设计模式,它可以将请求发送者与接收者解耦,请求发送者通过命令对象来间接引用请求接收者,使得系统具有更好的灵活性和可扩展性。
优点
(1)降低系统的耦合度。
(2)新的命令可以很容易地加入到系统中。由于增加新的具体命令类不会影响到其它类,因此增加新的具体命令类很容易,无须修改原有系统源代码,甚至客户端代码,满足开闭原则的要求。
(3)可以比较容易地设计一个命令队列或宏命令(组合命令)。
(4)为请求的撤销(Undo)和恢复(Redo)操作提供了一种设计和实现方案。
缺点
使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个对请求接收者的调用操作都需要设计一个具体命令类,所以在某些系统中可能需要提供大量的具体命令类,这将影响命令模式的使用。
适用环境
(1)系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。请求调用者无须知道接收者的存在,也无须知道接收者是谁,接收者也无须关心何时被调用。
(2)系统需要在不同的时间指定请求,将请求排队和执行请求。一个命令对象和请求的初始调用者可以有不同的生命周期,换而言之,最初的请求发出者可能已经不在了,而命令对象本身仍然是活动的,可以通过该命令对象去调用请求接收者,并且无须关心请求调用者的存在性,可以通过请求日志文件等机制来具体实现。
(3)系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
(4)系统需要将一组操作组合在一起形成宏命令。
以上文字,大量摘抄自《Java设计模式》一书,由刘伟老师编著,故本文应当列入转载一类,有兴趣的朋友可以直接阅读原书。