题记
在计算机中,很多时候当你遇到一个棘手的问题时,尝试在中间加一层,或许你的问题就会迎刃而解。
概述
有一个控制电灯开关的遥控器,要求它能控制不同厂商生产的电灯,而且不同厂商生产的电灯提供的接口又不同,比如厂商A开关灯函数是lightOn和lightOff,而厂商B开关灯函数是lightStart和lightStop。这个时候你肯定不会像在遥控器的实现代码中先分别判断厂商类型,再根据不同厂商类型调用具体类型的函数,这明显不符合开闭原则,万一哪天来了一个厂商C呢,你就不得不再次修改遥控器的实现代码。这个时候,你再看看本文的题记,或许你就有思路了。
命令模式
命令模式就是用新的命令对象包装原有对象的请求,调用者再利用这个命令对象来完成原始的请求。整体上看来,就是在调用者和接收者的中间加了一层,这一层就是命令对象,以达到将调用者和接收者解耦的目的。类图如下:
命令模式
如上图,命令模式的命令对象为了对外提供统一的方法,因此采用了一个类实现一个接口的方式,Receiver就是实际的功能提供者,提供doSomething方法,Invoker是功能的调用者,它需要调用Receiver的doSomething方法,但如果直接调用,就会代码紧密耦合,所有采用了ConcreteCommand这个命令对象来做一层媒介,这样Invoker就只管调用Command接口提供的方法,而不用在意底层到底是什么对象,关心底层对象的责任转移到了ConcreteCommand命令对象中来,这就使得Invoker和Receiver彻底解耦。ConcreteCommand对象的execute方法调用具体receiver对象的doSomething完成具体的请求。注意,命令模式可以方便的支持撤销操作,就是那个undo方法,这使得命令模式被用于事物,日志等场景,比如在事物中执行完execute后发现有问题,就可以再调用undo方法回到原来的状态。
接下来完成遥控器的例子:
A,B厂商提供的电灯:
public class LightA {
public void lightOn(){
System.out.println("A厂电灯开启");
}
public void lightOff(){
System.out.println("A厂电灯关闭");
}
}
public class LightB {
public void lightStart(){
System.out.println("B厂电灯开启");
}
public void lightStop(){
System.out.println("B厂电灯关闭");
}
}
命令对象要实现的接口:
public interface Command {
public void execute();
public void undo();
}
两种电灯的开关命令:
public class LightAOnCommand implements Command{
private LightA lightA;
public LightAOnCommand(LightA lightA) {
this.lightA = lightA;
}
@Override
public void execute() {
lightA.lightOn();
}
@Override
public void undo() {
lightA.lightOff();
}
}
public class LightAOffCommand implements Command{
private LightA lightA;
public LightAOffCommand(LightA lightA) {
this.lightA = lightA;
}
@Override
public void execute() {
lightA.lightOff();
}
@Override
public void undo() {
lightA.lightOn();
}
}
public class LightBOnCommand implements Command{
private LightB lightB;
public LightBOnCommand(LightB lightB) {
this.lightB = lightB;
}
@Override
public void execute() {
lightB.lightStart();
}
@Override
public void undo() {
lightB.lightStop();
}
}
public class LightBOffCommand implements Command{
private LightB lightB;
public LightBOffCommand(LightB lightB) {
this.lightB = lightB;
}
@Override
public void execute() {
lightB.lightStop();
}
@Override
public void undo() {
lightB.lightStart();
}
}
空命令:
public class NoCommand implements Command{
@Override
public void execute() {
System.out.println("空命令");
}
@Override
public void undo() {
System.out.println("空命令");
}
}
遥控器代码:
public class Telecontroller {
private Command lightOnCommand;
private Command lightOffCommand;
private Command currentCommand;
public Telecontroller(Command lightOnCommand,Command lightOffCommand,Command currentCommand) {
this.lightOnCommand = lightOnCommand;
this.lightOffCommand = lightOffCommand;
this.currentCommand = currentCommand;
}
public void lightOn(){
lightOnCommand.execute();
currentCommand = lightOnCommand;
}
public void lightOff(){
lightOffCommand.execute();
currentCommand = lightOffCommand;
}
public void undo(){
currentCommand.undo();
}
public void setLightOnCommand(Command lightOnCommand) {
this.lightOnCommand = lightOnCommand;
}
public void setLightOffCommand(Command lightOffCommand) {
this.lightOffCommand = lightOffCommand;
}
public void setCurrentCommand(Command currentCommand) {
this.currentCommand = currentCommand;
}
}
客户端代码:
public class Main {
public static void main(String[] args) {
LightA lightA = new LightA();
LightB lightB = new LightB();
LightAOnCommand lightAOnCommand = new LightAOnCommand(lightA);
LightAOffCommand lightAOffCommand = new LightAOffCommand(lightA);
LightBOnCommand lightBOnCommand = new LightBOnCommand(lightB);
LightBOffCommand lightBOffCommand = new LightBOffCommand(lightB);
//刚开始用A厂的电灯
Telecontroller telecontroller = new Telecontroller(lightAOnCommand,lightAOffCommand,new NoCommand());
telecontroller.lightOn();//开灯
telecontroller.lightOff();//关灯
telecontroller.undo();//撤销,即撤销关灯操作,就是开启电灯
System.out.println("========================================");
//现在换成B厂的电灯
telecontroller.setCurrentCommand(new NoCommand());
telecontroller.setLightOnCommand(lightBOnCommand);
telecontroller.setLightOffCommand(lightBOffCommand);
telecontroller.lightOn();//开灯
telecontroller.lightOff();//关灯
telecontroller.undo();//撤销,即撤销关灯操作,就是开启电灯
}
}
运行结果:
以上代码中,控制器和电灯实现了解耦,正如主函数中所看到的,命令对象对电灯包装,控制器只依赖命令对象,当控制器要完成开灯操作时,只管调用Command的execute方法即可,至于具体调用的是A还是B厂的电灯,控制器不关心,这就是解耦的表现,当要把A厂的电池全部换成B厂时,只用设置控制器的命令对象为B厂的命令对象即可。
命令对象其它用法
如果一个命令对象中包含了一个命令对象数组,即在execute方法中可以调用很多其它命令对象的execute方法,那么就构成了一个宏命令,简单来说就是构建一个新的命令对象,它用来批量执行已有的命令对象的execute方法,即是对现有命令的一个组合。命令模式还被广泛用于队列,事物,日志等场景。