这篇博客我们来介绍一下命令模式(Command Pattern),它是行为型设计模式之一。命令模式相对于其他的设计模式更为灵活多变,我们接触比较多的命令模式个例无非就是程序菜单命令,如在操作系统中,我们点击关机命令,系统就会执行一系列的操作,如先是暂停处理事件,保存系统的一些配置,然后结束程序进程,最后调用内核命令关闭计算机等,对于这一系列的命令,用户不用去管,用户只需点击系统的关机按钮即可完成如上一系列的命令。而我们的命令模式其实也与之相同,将一系列的方法调用封装,用户只需调用一个方法执行,那么所有的这些被封装的方法就会被挨个执行调用。
转载请注明出处:http://blog.csdn.net/self_study/article/details/52091539。
PS:对技术感兴趣的同鞋加群544645972一起交流。
设计模式总目录
特点
将一个请求封装成一个对象,从而让用户使用不同的请求把客户端参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。
命令模式的使用场景:
- 需要抽象出待执行的动作,然后以参数的形式提供出来—类似于过程设计中的回调机制,而命令模式正是回调机制的一个面向对象的替代品;
- 在不同的时刻指定、排列和执行请求,一个命令对象可以有与初始请求无关的生存期;
- 需要支持取消操作;
- 支持修改日志功能,这样当系统崩溃时,这些修改可以被重做一遍;
- 需要支持事务操作;
- 系统需要将一组操作组合在一起,即支持宏命令。
wiki 上列出的具体使用场景:
- GUI buttons and menu items
- Macro recording
- Mobile Code
- Multi-level undo
- Networking
- Parallel Processing
- Progress bars
- Thread pools
- Transactional behavior
- Wizards
在 Java Swing 和 Delphi 语言中,一个 Action 是一个命令,除了执行预定的命令之外,一个 Action 可能会有一个相关联的图标,键盘快捷键,气泡提示文本等等。一个工具栏按钮或者菜单的元素可能完全用一个 Action 对象进行初始化。
如果所有的用户动作都代表了一个个命令对象,那么一个程序就能够轻易的保存一系列的命令对象,之后能够将这一系列的命令重新执行一遍来实现一个回播的动作。如果程序中嵌入了脚本引擎,那么每个命令对象都能够实现 toScript() 方法,用户的动作也能够被轻松的保存为脚本对象。
类似于 Java 这种能够通过 URLClassloders 将代码变成流从一个地方传输到另一个地方的语言,并且代码库中的命令使新的行为能够被传递到远程位置(EJB Command,Master Worker模式)。
如果程序中所有的用户动作都被实现成了命令对象,程序就能够保存最近被执行的命令对象,当用户想要撤销某些命令时,程序就可以简单的 pop 出最近的命令并且执行他的 undo 方法。
能够将所有的命令对象通过网络传输到另外一台设备上去执行,比如端游中的用户操作等。
所有的命令都被写成了共享资源中的任务并且并发的被很多线程同时执行(在远程机器上这种变体可能这个会被称为 Master/Worker 模式)。
我们假定程序有一系列需要按顺序执行的命令,如果每个命令对象都有一个 getEstimatedDuration() 方法,那么程序就可以轻松估算出整体的执行时间,然后展示一个有意义的进度条来反应当前所有任务的执行程度。
一个具有代表性的线程池可能会有一个 public 的 addTask() 方法用来添加一个工作任务到内部的等待队列中,这个线程池中会有一系列的线程用来执行队列中的一系列命令对象。普遍的,这些对象会实现一个通用的接口,比如 Runnable 等,用来允许线程池执行这些命令,虽然这个线程池类并不了解它会被用来处理具体的什么任务。
类似于 undo 操作,一个数据库引擎或者软件安装器可能会维护一个已经执行或者将要执行的操作列表,如果其中的一个失败了,所有其他的都会被还原或者抛弃(通常被称为 rollback,回滚)。举个例子,如果相关联的两个数据库表必须要更新,并且第二个更新失败,这个事务将会被回滚,所以第一个表的更新也会被抛弃。
向导页是用户在点击最后一页”结束”按钮时候弹出来的几页配置页(配置用户的使用习惯等),在这种情况下,一个通常分离用户操作代码和程序代码的方法就是使用命令对象实现向导功能。这个命令对象会在向导页第一次展示的时候被创建,每个向导页将它们自己的 GUI 变化保存在一个命令对象中,所以这个对象被定位为用户的进一步操作。“结束”动作简单的触发了一个 excete() 动作,这样一来,这个命令类将会达到预期的效果。
UML类图
我们来看看命令模式的 uml 类图:
命令模式的角色介绍:
- Receiver:接收者角色
- Command :命令接口
- ConreteCommand:具体命令角色
- Invoker :请求者角色
- Client:客户端角色
该类负责具体实施或执行一个请求,说的通俗一点就是,执行具体逻辑的角色,以上面说到的“关机”操作命令为例,其接收者角色就是真正执行各项关机逻辑的底层代码。任何一个类都能成为一个接收者,而接收者类中封装具体操作逻辑的方法我们则称为行动方法;
定义所有具体命令类基本行为的抽象接口;
该类实现了 Command 接口,在 execute 方法中调用接收者角色的相关方法,在接收者和命令执行的具体行为之间加以弱耦合。而 execute 则通常称为执行方法,如上面提到的“关机”操作实现,具体可能还包含很多相关的操作,比如保存数据、关闭文件、结束进程等,如果将这一些列的具体逻辑处理看作接收者,那么调用这些具体逻辑的方法就可以看作是执行方法;
该类的职责就是调用命令对象执行具体的请求,相关的方法我们称为行动方法,“关机”命令为例,关机这个命令一般就对应着一个关机方法,执行关机命令就相当于由这个关机方法去执行具体的逻辑,这个关机方法就可以看作是请求者;
由此我们可以写出命令模式的通用代码:
Receiver.class 具体逻辑执行者
public class Receiver {
public void action() {
System.out.print("执行具体的操作");
}
}
Command.class 抽象命令类
public interface Command {
void execute();
}
ConcreteCommand.clas 具体命令类
public class ConcreteCommand implements Command {
private Receiver receiver;
public ConcreteCommand(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
receiver.action();
}
}
Invoker.class 请求者
public class Invoker {
private Command command;
public Invoker(Command command) {
this.command = command;
}
public void action() {
command.execute();
}
}
Client 客户端
public class Client {
public static void main(String[] args) {
Receiver receiver = new Receiver();
Command command = new ConcreteCommand(receiver);
Invoker invoker = new Invoker(command);
invoker.action();
}
}
最后也能通过 Invoker 类调用到真正的 Receiver 执行逻辑了。
示例与源码
这就以一个简单的控制电灯亮灭和门开关的情景为例:
Light.class 和 Door.class 实际控制类
public class Light {
public void lightOn() {
System.out.print("light on\n");
}
public void lightOff() {
System.out.print("light off\n");
}
}
public class Door {
public void doorOpen() {
System.out.print("door open\n");
}
public void doorClose() {
System.out.print("door close\n");
}
}
然后是电灯的控制类:
LightOnCommand.class 和 LightOffCommand.class
public class LightOnCommand implements Command{
public Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.lightOn();
}
}
public class LightOffCommand implements Command{
public Light light;
public LightOffCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.lightOff();
}
}
门的相关控制类:
DoorOpenCommand.class 和 DoorCloseCommand.class
public class DoorOpenCommand implements Command{
public Door door;
public DoorOpenCommand(Door door) {
this.door = door;
}
@Override
public void execute() {
door.doorOpen();
}
}
public class DoorCloseCommand implements Command{
public Door door;
public DoorCloseCommand(Door door) {
this.door = door;
}
@Override
public void execute() {
door.doorClose();
}
}
然后是一个无操作默认命令类:
NoCommand.class
public class NoCommand implements Command{
@Override
public void execute() {
}
}
最后是控制类:
Controller.class
public class Controller {
private Command[] onCommands;
private Command[] offCommands;
public Controller() {
onCommands = new Command[2];
offCommands = new Command[2];
Command noCommand = new NoCommand();
onCommands[0] = noCommand;
onCommands[1] = noCommand;
offCommands[0] = noCommand;
offCommands[1] = noCommand;
}
public void setCommand(int slot, Command onCommand, Command offCommand) {
onCommands[slot] = onCommand;
offCommands[slot] = offCommand;
}
public void onCommand(int slot) {
onCommands[slot].execute();
}
public void offCommand(int slot) {
offCommands[slot].execute();
}
}
测试代码
Light light = new Light();
Door door = new Door();
LightOnCommand lightOnCommand = new LightOnCommand(light);
LightOffCommand lightOffCommand = new LightOffCommand(light);
DoorOpenCommand doorOpenCommand = new DoorOpenCommand(door);
DoorCloseCommand doorCloseCommand = new DoorCloseCommand(door);
Controller controller = new Controller();
controller.setCommand(0, lightOnCommand, lightOffCommand);
controller.setCommand(1, doorOpenCommand, doorCloseCommand);
controller.onCommand(0);
controller.offCommand(0);
controller.onCommand(1);
controller.offCommand(1);
结果:
这样就实现了对 Light 和 Door 的控制了,其实这个例子只是实现了对命令模式的基本框架而已,命令模式的用处其实在于它的日志和回滚撤销功能,每次执行命令的时候都打印出相应的关键日志,或者每次执行后都将这个命令保存进列表中并且每个命令实现一个 undo 方法,以便可以进行回滚。另外,也可以构造一个 MacroCommand 宏命令类用来按次序先后执行几条相关联命令。当然可以发散的空间很多很多,感兴趣的可以自己去实现,原理都是一样的。
总结
命令模式将发出请求的对象和执行请求的对象解耦,被解耦的两者之间通过命令对象进行沟通,调用者通过命令对象的 execute 放出请求,这会使得接收者的动作被调用,调用者可以接受命令当作参数,甚至在运行时动态地进行,命令可以支持撤销,做法是实现一个 undo 方法来回到 execute 被执行前的状态。MacroCommand 宏命令类是命令的一种简单的延伸,允许调用多个命令,宏方法也可以支持撤销。日志系统和事务系统可以用命令模式来实现。
命令模式的优点很明显,调用者和执行者之间的解耦,更灵活的控制性,以及更好的扩展性等。缺点更明显,就是类的爆炸,大量衍生类的创建,这也是大部分设计模式的“通病”,是一个没有办法避免的问题。
源码下载
https://github.com/zhaozepeng/Design-Patterns/tree/master/CommandPattern