先看例子:
//灯:有开、关两个操作
public class Light {
public void off(){
System.out.println("off...");
}
public void on(){
System.out.println("on...");
}
}
//命令
public interface Command {
public void execute();
}
public class LightOnCommand implements Command {
Light light;
public LightOnCommand(Light light){
this.light = light;
}
public void execute() {
light.on();
}
}
public class LightOffCommand implements Command {
Light light;
public LightOffCommand(Light light){
this.light = light;
}
public void execute() {
light.off();
}
}
//遥控器
public class RemoteControl {
Command c;
public void setCommand(Command c){
this.c = c;
}
public void pressButton(){
c.execute();
}
}
测试:
public class CommandLoader {
public static void main(String[] args) {
//遥控器对象
RemoteControl src = new RemoteControl();
//灯对象
Light light = new Light();
LightOnCommand lo = new LightOnCommand(light);
//LightOffCommand lo = new LightOffCommand(light);
src.setCommand(lo);
src.pressButton();
}
}
运行结果是:
on...
我们分析下这段代码可知,程序的最终目的是通过“遥控器”控制“灯”打开,效果类似于在RemoteControl类的pressButton()方法里直接:
Light light = new Light();
light.on();
这样很直接,也很好理解,但是灵活性不好,如果我们要换一种“灯”或者给“灯”添加一些新的操作,比如brighter()调高亮度、dim()调低亮度,就必须修改RemoteControl类了,根本原因在于上面RemoteControl类中使用了硬编码,我们把具体的类Light放进了RemoteControl类中,殊不知这个具体的东西以后可能常常有变化。
再分析下,我们上面这种写法,让RemoteControl类针对抽象的Command接口,而将Light的具体操作封装进Command的实现类中,相当于在RemoteControl和Light中间人为的加了一层,RemoteControl针对Command编程,而一个具体的Command实际上就代表了原来Light里的一个操作。
这就是命令模式:请一个请求封装成对象,从而可以使用不同的请求对客户进行参数化。
下面是命令模式的类图模型:
Invoker:命令的调用者,但却不直接与命令的实施者Receiver关联,而是关联一个Command引用;
Command:(一般用接口)表示命令,给所有需要执行的实际操作一个统一的“接口”;
ConcreteCommand: 具体的操作命令,实现Command接口,同时关联一个Receiver,表示作用在哪个实施者上面的操作;
Receiver:命令的实施者,可将它的每一种操作都封装成一个独立的命令对象,比如LightOnCommand、LightOffCommand、LightXXXCommand等,它们都实现Command接口,从而有一个共同的身份Command;
CommandLoader: 负责组装Reveiver和ConceteCommand,实际的客户端操作Invoker对象。
仔细分析命令模式,可以看出它有很多好处:
1、请求的调用者与请求的实施者解耦
Invoker(请求调用者)不用像以前一样知道每一Receiver(请求的实施者),假如我们要扩展Light,例如添加一个dim()方法调低亮度,RemoteControl完全不用修改,只用添加一个LightDimCommand命令,在把它动态组装到ConreteCommand中即可。
2、可实现撤销功能
不使用命令模式时,light.on()就是一个原子操作,执行完了就完了,而命令模式中由于我们把这个操作封装进了一个对象中,而对象是一个实实在在的有状态的东西,我们可以把它保存起来,这样即使操作结束,由于我们之前保存了那个命令对象,我们也就知道它到底干了什么,当然也就可以撤销了。
我们甚至可以多级撤销,只要将命令对象存放进一个stack栈中,之后便可以轻松取出来。
总结:
命令模式的关键点在于:共同的接口Command、用Command对象封装每一个请求。另外,对象有状态、可以保存,以至于我们可以拿这些对象做一些扩展的功能。