一、概述
将一个请求封装为一个对象,从而可用不同的请求对客户进行参数化,对请求排队或者记录请求日志,以及支持可撤回的操作。
它将发送者与接收者解耦,发送者通过命令对象来间接引入接收者,使得系统具有更好的灵活性。
二、结构与实现
- Invoker(调用者):
通过命令对象来执行请求,与抽象命令类之间存在关联关系,运行时再将一个具体命令类注入其中,再具体调用命令对象的execute()方法,从而实现间接调用请求接收者的相关操作。
- Command(抽象命令类):
声明了用于执行请求的execute()的方法,通过这些方法可以调用请求接收者的相关操作。
- ConcreteCommand(具体命令类):
抽象命令类的子类,实现其中的方法,它对应具体的接收者对象,将接收者对象的动作绑定其中。具体命令类在实现execute()方法时将调用接收者对象的相关操作(Action)。
- Receiver(接收者):
接收者执行与请求相关的操作,具体实现对请求的业务处理。
- 代码实现
abstract class Command{
public abstract void execute();
}
class Invoker{
private Command command;
public Invoker(Command command){
this.command=command;
}
public void setCommand(Command command){
this.command=command;
}
public void execute(){
command.execute();
}
}
class Receiver{
public void action(){
//具体操作
}
}
class ConcreteCommand extends Command{
private Receiver receiver;
public void execute(){
receiver.action();
}
}
三、应用案例
- 分析:
功能键FunctionButton即调用者,退出系统跟显示帮助文档为接收者。ExitCommand和HelpCommand为具体的命令对象。
- 类图:
- 代码实现:
//抽象命令
abstract class Command{
public abstract void execute();
}
//调用者
class FunctionButton{
private Command command;
public FunctionButton(Command command) {
this.command = command;
}
public void setCommand(Command command) {
this.command = command;
}
public void Click(){
System.out.println("单击了按钮!");
command.execute();
}
}
//(退出系统和帮助文档)接收者
class SystemExitClass{
public void exit(){
System.out.println("退出系统...");
}
}
class DisplayHelpClass{
public void display(){
System.out.println("显示帮助文档...");
}
}
//具体调用者
class ExitCommand extends Command{
private SystemExitClass systemExitClass;
public ExitCommand(){
systemExitClass=new SystemExitClass();
}
@Override
public void execute() {
systemExitClass.exit();
}
}
class HelpCommand extends Command{
private DisplayHelpClass displayHelpClass;
public HelpCommand(){
displayHelpClass=new DisplayHelpClass();
}
@Override
public void execute() {
displayHelpClass.display();
}
}
//test
public class Client {
public static void main(String[] args) {
//退出系统
FunctionButton functionButton=new FunctionButton(new ExitCommand());
functionButton.Click();
//显示帮助文档
FunctionButton functionButton1=new FunctionButton(new HelpCommand());
functionButton1.Click();
}
}
四、扩展
- 实现命令队列
(1)当一个请求发出后,不止一个接收者对其产生响应,这些接收者将逐个执行业务方法,完成对请求的处理,此时就可以通过命令队列来实现。
(2)最常用,灵活性最好的一种方式是增加一个CommandQueue类,该类存储多个命令对象,而不同的命令对象可以对应不同的请求接收者。
class CommandQueue{
private List<Command> list=new ArrayList<Command>();
public void addCommand(Command command){
list.add(command);
}
public void removeCommand(Command command){
list.remove(command);
}
public void execute(){
for(Object command:list){
((Command)command).execute();
}
}
}
(3)增加命令队列类CommandQueue以后,请求发送者Invoker将针对CommandQueue编程。
class Invoker{
private CommandQueue commandQueue;
public Invoker(CommandQueue commandQueue){
this.commandQueue=commandQueue;
}
public void setCommandQueue(CommandQueue commandQueue){
this.commandQueue=commandQueue;
}
public void call(){
commandQueue.execute();
}
}
(4)命令队列与人们常说的批处理有点类似。批处理,顾名思义,可以对一组命令对象进行批处理,当一个发送者发送请求后将有一系列接收者对请求作出响应,命令队列可以用于设计批处理应用程序。
- 记录请求日志
(1)请求日志就是将请求的历史记录保存下来,通常以日志文件的形式永久保存。
(2)请求日志文件可实现的功能:实现批处理,一个请求日志文件中可以存储一系列的命令对象;为系统提供恢复机制,在请求日志文件中可以记录用户对系统的每一步操作,从而让系统恢复到特定的状态;可以将所有的命令对象存储在一个日志文件中,每执行一个命令删除一个命令对象,防止因断电或系统重启造成的请求丢失,也可以避免某些命令的重复执行。
(3)实现请求日志时可以将发送请求的命令对象通过序列化写到日志文件中,此时命令类必须实现Serializable接口。
- 实现撤销操作
抽象命令类中声明undo(撤销)方法,在具体命令类实现undo方法,通过加一个相反数来实现加法的撤销操作。
五、总结
- 该模式包含四个角色:请求调用者,请求接收者,抽象命令类,具体命令类。
- 调用者聚合抽象命令,对抽象命令对象进行编程。抽象命令对象定义execute方法,当调用者调用方法时,实际上调用的是抽象命令对象的execute方法,而execute在具体对象中,聚合了接收者,所以最终该execute方法调用的是接收者的方法。
- 适用环境:系统需要支持命令的撤销和恢复操作;系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。