目录
命令模式
命令模式是常用的行为型设计模式之一,它将请求发送者与请求接收者解耦,请求发送者通过命令对象来间接引用接收者,使得系统具有更好的灵活性,可以在不修改现有系统源代码的情况下让相同的发送者对应不同的接收者.
命令模式概述
命令模式:将一个请求封装为一个对象,从而可用不同的请求对客户进行参数化,对请求排队或者记录请求日志,以及支持可撤销的操作.
命令模式是一种对象行为型模式,其别名为动作模式或事务模式.
命令模式结构
命令模式包含4个角色:
- 抽象命令类.
- 具体命令类.
- 调用者.
- 接收者.
命令模式实现
抽象命令类
public abstract class Command{
public 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类,由该类来负责存储多个命令对象,而不同的命令对象可以对应不同的请求接收者,CommandQueue类的典型代码如下所示:
import java.util.*;
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 (Object command : commands) {
((Command)command).execute();
}
}
}
在增加了命令队列类CommandQueue以后,请求发送者类Invoker将针对CommandQueue编程,代码修改如下:
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();
}
}
命令队列与我们常说的“批处理”有点类似.批处理,顾名思义,可以对一组对象(命令)进行批量处理,当一个发送者发送请求后,将有一系列接收者对请求作出响应,命令队列可以用于设计批处理应用程序,如果请求接收者的接收次序没有严格的先后次序,我们还可以使用多线程技术来并发调用命令对象的execute()方法,从而提高程序的执行效率.
记录请求日志
请求日志就是将请求的历史记录保存下来,通常以日志文件的形式永久存储在计算机中.请求日志文件可以实现很多功能,常用功能如下:
- 一旦系统发生故障,日志文件可以为系统提供一种恢复机制,在请求日志文件中可以记录用户对系统的每一步操作,从而让系统能够顺利恢复到某一个特定的状态.
- 请求日志也可以用于实现批处理,在一个请求日志文件中可以存储一系列命令对象,例如一个命令队列.
- 可以将命令队列中的所有命令对象都存储在一个日志文件中,每执行一个命令则从日志文件中删除一个对应的命令对象,防止因为断电或者系统重启等原因造成请求丢失,而且可以避免重新发送全部请求时造成某些命令的重复执行,只需读取请求日志文件,再继续执行文件中剩余的命令即可.
在实现请求日志时可以将发送请求的命令对象通过序列化写到日志文件中.
实现撤销操作
设计一个简易计算器,该计算器可以实现简单的数学运算,还可以对运算实施撤销操作.
//加法类:请求接收者
class Adder {
private int num=0; //定义初始值为0
//加法操作,每次将传入的值与num作加法运算,再将结果返回
public int add(int value) {
num += value;
return num;
}
}
//抽象命令类
abstract class AbstractCommand {
public abstract int execute(int value); //声明命令执行方法execute()
public abstract int undo(); //声明撤销方法undo()
}
//具体命令类
class ConcreteCommand extends AbstractCommand {
private Adder adder = new Adder();
private int value;
//实现抽象命令类中声明的execute()方法,调用加法类的加法操作
public int execute(int value) {
this.value=value;
return adder.add(value);
}
//实现抽象命令类中声明的undo()方法,通过加一个相反数来实现加法的逆向操作
public int undo() {
return adder.add(-value);
}
}
//计算器界面类:请求发送者
class CalculatorForm {
private AbstractCommand command;
public void setCommand(AbstractCommand command) {
this.command = command;
}
//调用命令对象的execute()方法执行运算
public void compute(int value) {
int i = command.execute(value);
System.out.println("执行运算,运算结果为:" + i);
}
//调用命令对象的undo()方法执行撤销
public void undo() {
int i = command.undo();
System.out.println("执行撤销,运算结果为:" + i);
}
}
客户端测试代码
class Client {
public static void main(String args[]) {
CalculatorForm form = new CalculatorForm();
AbstractCommand command;
command = new ConcreteCommand();
form.setCommand(command); //向发送者注入命令对象
form.compute(10);
form.compute(5);
form.compute(10);
form.undo();
}
}
需要注意的是在本实例中只能实现一步撤销操作,因为没有保存命令对象的历史状态,可以通过引入一个命令集合或其他方式来存储每一次操作时命令的状态,从而实现多次撤销操作.除了Undo操作外,还可以采用类似的方式实现恢复(Redo)操作,即恢复所撤销的操作(或称为二次撤销).
除了通过一个逆向操作来实现撤销(Undo)外,还可以通过保存对象的历史状态来实现撤销,后者可使用备忘录模式来实现.
宏命令
宏命令又称为组合命令,它是组合模式和命令模式联用的产物.宏命令是一个具体命令类,它拥有一个集合,在该集合中包含了对其他命令对象的引用.通常宏命令不直接与请求接收者交互,而是通过它的成员来调用接收者的方法.
当调用宏命令的execute()方法时,将递归调用它所包含的每个成员命令的execute()方法,一个宏命令的成员可以是简单命令,还可以继续是宏命令.执行一个宏命令将触发多个具体命令的执行,从而实现对命令的批处理,其结构如图7所示:
命令模式优缺点与适用环境
命令模式优点:
- 降低了系统的耦合度,增加新的命令很容易,可以比较容易地设计一个命令队列或宏命令,还为请求的撤销和恢复操作提供了一种设计和实现方案.
命令模式缺点:
- 使用命令模式会导致某些系统有过多的具体命令类.
命令模式适用环境:
- 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互.请求调用者无须知道接收者的存在也无须知道接收者是谁,接收者也无须关心何时被调用.
- 系统需要在不同的时间指定请求,将请求排队和执行请求.一个命令对象和请求的初始调用者可以有不同的生命期,换言之,最初的请求发出者可能已经不在了,而命令对象本身仍然是活动的,可以通过该命令对象去调用请求接收者,而无须关心请求调用者的存在性,可以通过请求日志文件等机制来具体实现.
- 系统需要支持命令的撤销操作和恢复操作.
- 系统需要将一组操作组合在一起形成宏命令.