软件设计模式学习(十八)命令模式
命令模式将请求发送者与请求接收者解耦,在发送者与接收者之间引入命令对象,将发送者的请求封装在命令对象中,请求发送者通过命令对象来间接引用接收者,使得系统具有更好的灵活性,用户可以根据需要为请求发送者增加新的命令对象而无须修改原有系统
模式动机
举个现实生活中的例子,开关是请求的发送者,电灯是请求的接收者,它们之间不存在直接的耦合关系,而是通过电线连接到一起,开关不需要知道如何将开灯或关灯请求传输给电灯,而是通过电线来完成这项功能。
此时可以理解为电线充当封装请求的命令对象,开关如果开则电线通电,并调用电灯的开灯方法,反之则关灯。不同电线可以连接不同的请求接收者,因此只需更换一根电线,相同的开关即可操作不同的电器设备。
在软件设计中,我们也像上述例子一样,经常需要向某些对象发送请求,但是不知道请求接收者是谁,也不知道被请求的操作是哪个,我们只需指定具体的请求接收者即可,此时,可以使用命令模式使请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用更加啊灵活。
模式定义
请一个请求封装为一个对象,从而使我们可用不同请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。命令模式是一种对象行为型模式,其别名为动作(Action)模式或事务(Transaction)模式
模式结构
-
Command(抽象命令类)
一般是接口,其中声明了用于执行请求的 execute() 等方法,通过这些方法调用请求接收者的相关操作
-
ConcreteCommand(具体命令类)
是抽象命令类的子类,实现在抽象命令类中声明的方法。它对应具体接收者对象,绑定接收者对象的动作。在实现 execute() 方法时将调用接收者对象的相关操作(Action)
-
Invoker(调用者)
即请求的发送者,又称请求者,通过命令对象来执行请求。一个调用者并不需要再设计时确定其接收者,因此它与抽象命令类之间只存在关联关系。程序运行时调用具体命令对象的 execute() 方法,间接调用接收者的相关操作
-
Receiver(接收者)
执行者执行与请求相关的操作,它具体实现对请求的业务处理
-
Client(客户类)
客户类中需创建调用者对象和具体命令对象,再创建具体命令对象时指定其对应接收者,发送者和接收者之间无直接关系,透过具体命令对象实现间接调用
模式分析
命令模式的本质其实就是将命令(Command)、发出命令的责任(Invoker)和执行命令的责任(Recevier)分隔开。请求的一方发出请求,要求执行一个操作,接收的一方收到请求,并执行操作,请求的一方不必知道接收请求一方的任何细节。
命令模式的关键在于引入抽象命令接口,调用者针(Invoker)对抽象命令接口编程,只有实现具体命令类才能与对应接收者相关联。每个具体命令类把接收者(Recevier)作为一个实例变量存储,从而指定接收者,并调用对应的请求处理方法
可以通过顺序图来进一步理解命令模式中对象之间的相互关系。
模式实例之电视机遥控器
电视机是请求接收者,遥控器是请求发送者,遥控器上有一些不同按钮,对应电视机的不同操作,分别是:打开电视机、关闭电视机和切换频道。
-
接收者类 Television(电视机类)
public class Televison { public void open() { System.out.println("打开电视机"); } public void close() { System.out.println("关闭电视机"); } public void changeChannel() { System.out.println("切换电视频道"); } }
-
抽象命令类 AbstractCommand(命令类)
public interface AbstractCommand { public void execute(); }
-
具体命令类 TVOpenCommand(电视机打开命令类)
public class TVOpenCommand implements AbstractCommand { private Televison tv; public TVOpenCommand() { tv = new Televison(); } @Override public void execute() { tv.open(); } }
-
具体命令类 TVCloseCommand(电视机关闭命令类)
public class TVCloseCommand implements AbstractCommand { private Televison tv; public TVCloseCommand() { tv = new Televison(); } @Override public void execute() { tv.close(); } }
-
具体命令类 TVChangeCommand(电视机频道切换命令类)
public class TVChangeCommand implements AbstractCommand { private Televison tv; public TVChangeCommand() { tv = new Televison(); } @Override public void execute() { tv.changeChannel(); } }
-
调用者类 Controller(遥控器类)
public class Controller { private AbstractCommand openCommand, closeCommand, changeCommand; public Controller(AbstractCommand openCommand, AbstractCommand closeCommand, AbstractCommand changeCommand) { this.openCommand = openCommand; this.closeCommand = closeCommand; this.changeCommand = changeCommand; } public void open() { openCommand.execute(); } public void change() { changeCommand.execute(); } public void close() { closeCommand.execute(); } }
-
客户端测试类 Client
public class Client { public static void main(String[] args) { AbstractCommand openCommand, closeCommand, changeCommand; openCommand = new TVOpenCommand(); closeCommand = new TVCloseCommand(); changeCommand = new TVChangeCommand(); Controller controller = new Controller(openCommand, closeCommand, changeCommand); controller.open(); controller.change(); controller.close(); } }
-
运行结果
模式优缺点
命令模式优点:
- 降低系统耦合度
- 新的命令可以很容易地加入系统中
- 可以比较容易地设计一个设计一个命令队列和宏命令(组合命令)
- 可以方便实现对请求的 Undo 和 Redo
命令模式缺点:
- 使用命令模式可能导致某些系统有过多的具体命令类
撤销操作的实现
我们可以通过对命令类进行修改使得系统支持撤销操作和恢复操作,,抽象命令类(AbstractCommand)声明一个 undo() 方法
public interface AbstractCommand {
public void undo();
public void execute();
}
具体命令类(ConcreteCommand)实现在抽象命令类(AbstractCommand)中声明的 execute() 和 undo() 方法
public class ConcreteCommand implements AbstractCommand {
private Receiver receiver;
public ConcreteCommand() {
receiver = new Receiver();
}
@Override
public void execute() {
receiver.method;
}
@Override
public void undo() {
// 撤销 execute() 操作
}
}
调用者(Invoker)照常引用一个抽象命令 AbstractCommand 类型的对象 command,通过该 command 对象间接调用接收者 Receiver 类的业务方法
public class Invoker {
private AbstractCommand command;
public Invoker(AbstractCommand command) {
this.openCommand = openCommand;
}
public void method() {
command.execute();
}
public void undo() {
// 撤销操作
}
}
上述实例只能实现一步撤销操作,因为没有保存命令对象的历史状态,可以通过引入一个命令集合或其他方式来存储中间状态,从而实现多次撤销操作
宏命令
宏命令又称组合命令,它是命令模式和组合模式联用的产物。宏命令也是一个具体命令,不过它包含了对其他命令对象的引用,在调用宏命令的 execute() 方法,将递归调用它所包含的每个成员命令的 execute() 方法。一个宏命令的成员对象可以是简单命令,也可以继续是宏命令。