前言
最近看了命令模式,觉得很有意思,平时大家应该都有使用过,所以写一点总结体会分享给大家。
正文
首先我们先不谈什么是命令模式,直接写点东西:
实现一个电视遥控器的功能:
1、遥控器有两个键:开机键和关机键。
2、电视接收对应的命令信号,执行对应的操作。
ok,首先我们知道命令是一个抽象的概念,所以我们先写一个Command借口:
/**
* Created by li.zhipeng on 2017/9/26.
* 命令接口
*/
public interface Command {
void onCommand();
}
因为遥控器要绑定对应的电视机,所以我们的遥控器按钮构造方法里或者是setter能够设置绑定的电视机,这样我们创建一个基类,我们的目的是为了在基类复杂一些基本信息,例如按钮的位置,颜色等公有特性:
/**
* 遥控器按钮的基类
*/
public abstract class ControllerButton {
private TV tv;
public ControllerButton(TV tv){
this.tv = tv;
}
}
下面定义开机键和关机键的功能:
/**
* 开机功能
*/
public class OpenCommand implements Command {
@Override
public void onCommand() {
System.out.println("打开电视...");
tv.open();
}
}
/**
* 关机功能
*/
public class CloseCommand implements Command {
@Override
public void onCommand() {
System.out.println("关闭电视...");
tv.close();
}
}
使用时的代码:
TV tv = new TV();
Command openCommand = new OpenCommand(tv);
Command closeCommand = new CloseCommand(tv);
openCommand.onCommand();
closeCommand.onCommand();
功能的扩展
刚才我们已经完成了基本功能的开发,我们的类的定义还是比较严格的,基类和接口都只做自己相关的事情,我们定义的每一个类都尽可能的少承担起整个功能的责任,符合我们开发的基本原则。
例如,我们可以去掉Command接口,把onCommand移动到基类ControllerButton中,但是这样就加重了基类的负担,违背了我们开发的基本准则,也影响到了之后的功能更扩展,所以不建议这样做的。
但是很快新需求来了:
在发送命令的时候,需要同时发送一条指示灯闪闪闪的命令,这样用户知道自己的命令到底发没发出去,有助于用户体验。
现在只有两个按钮,我们只要在调用的时候,添加上指示灯闪闪的命令就可以了:
// 红灯闪闪命令
lampBulingCommand.onCommand();
openCommand.onCommand();
这个时候一定有人站出来了:
我现在就要把Command去掉,直接在基类ControllerButton里面实现onCommand方法,这样不用修改调用方法,比你这种实现吊太多!!!
不得不说这是一个好办法,以现在这个需求来看修改基类是最简单的,但是违背了我们之前的开发原则,但是为了这位朋友的任性,我们暂时不阻止他,于是基类的代码发生了改变:
/**
* 遥控器按钮的基类
*/
public abstract class ControllerButton implements Command{
protected TV tv;
/**
* 指示灯闪闪命令
* */
private LampBulingCommand lampBulingCommand;
public ControllerButton(TV tv){
this.tv = tv;
this.lampBulingCommand = lampBulingCommand;
}
/**
* 对onCommand进行包装
* */
public void sendCommand(){
if (lampBulingCommand != null){
lampBulingCommand.onCommand();
}
onCommand();
}
}
// 调用处,请注意这里已经无法Command,因为sendCommand定义在基类里,而不是在Command接口里
OpenCommand openCommand = new OpenCommand(tv);
openCommand.sendCommand();
我们看到了,为了实现产品的需求,他做了3处修改:
1、添加属性,保存指示灯闪闪命令。
2、修改构造方法,实例化指示灯闪闪命令。
3、在onCommand外部定义了一个中转函数,执行指示灯闪闪命令。
4、修改调用的代码。
5、最关键:已经无法通过Command类型创建方法。
但是他仍然陶醉在自己的世界里,认为这个方法屌爆了。
但是很遗憾,很快新需求又来了:
在新增一条记忆命令,我们需要统计开机的次数,但是不统计关机命令的次数。
虽然这个需求有点变态了,但是我们还得硬着头皮继续写,这个时候刚才的那位朋友又站出来了,分分钟就要解决这个问题:
/**
* 遥控器按钮的基类
*/
public abstract class ControllerButton implements Command{
protected TV tv;
/**
* 指示灯闪闪命令
* */
private LampBulingCommand lampBulingCommand;
/**
* 记忆命令
*/
private MemoryCommand memoryCommand;
public ControllerButton(TV tv){
this.tv = tv;
this.lampBulingCommand = lampBulingCommand;
memoryCommand = new MemoryCommand(tv);
}
/**
* 对onCommand进行包装
* */
public void sendCommand(){
// 指示灯闪闪
lampBulingCommand.onCommand();
// 如果是开机功能,要发送记忆命令
if (this instanceof OpenCommand) {
memoryCommand.onCommand();
}
onCommand();
}
}
经过修改后的代码,虽然运行正常,他自己已经感觉到自己挖的坑越来越深,而其他人也出现了怀疑的态度,因为:
1、父类已经开始影响到子类的业务逻辑。
2、基类越来越臃肿:每一次内部的属性的增加和sendCommand方法的复杂度的上升,都让基类的变得越来越臃肿,并且基类已经开始越权处理onCommand的逻辑,Command接口已经形同虚设,类的阅读和维护都开始出现了问题。
3、已经无法通过Command创建命令实例,全部要被替换成ControllerButton,类的语义出现了严重的危机。
命令模式登场
经过之前的讨论,命令模式终于登场了,于是咔咔咔重构了代码:
/**
* 命令的执行者,处于命令发起者和接收者之间,在这个过程中进行处理
*/
public class Switch {
private LampBulingCommand lampBulingCommand;
private MemoryCommand memoryCommand;
public Switch(TV tv) {
lampBulingCommand = new LampBulingCommand(tv);
memoryCommand = new MemoryCommand(tv);
}
public void excuteCommand(Command command) {
// 指示灯闪闪
lampBulingCommand.onCommand();
// 如果是开机功能,要发送记忆命令
if (command instanceof OpenCommand) {
memoryCommand.onCommand();
}
// 执行参数命令
command.onCommand();
}
}
修改调用的代码:
TV tv = new TV();
Command openCommand = new OpenCommand(tv);
Command closeCommand = new CloseCommand(tv);
//openCommand.onCommand();
//closeCommand.onCommand();
Switch s = new Switch(tv);
s.excuteCommand(openCommand);
s.excuteCommand(closeCommand);
是不是完全被惊艳到了,就是简单~
现在开始进入正题:什么是命令模式?
命令模式是把一个操作和他的参数,包装成一个新的对象去执行。
命令模式有四个部分:命令,接收者,执行者,发起者。
以刚才的demo为例,Command代表命令,接收者是TV,执行者是Switch,发起者也就是客户端。
从demo中看到,随着需求的变化,我们的每一次修改都要修改多个类,并且代码的成本也很高,于是通过Switch把Command的执行过程包装了起来,也就是在发起者和接收者之间,随意我们就可以根据需求,定制执行的过程。
也可以理解成,Switch把基类中有关于Command的功能全部抽取了出来,作为一个独立模块。
经过命令模式的重构,我们之后的扩展和修改,只要不改变open和close的核心功能,只要修改Switch类就可以了,这就是命令模式的优点。
总结
最后我们对命令模式进行一下总结:
1、命令模式是对某一个操作的和其参数的封装,目的是维护这个操作的过程。
2、命令模式位于发起者和接收者之间,对两者进行解耦,便于维护。
3、命令模式能够帮助我们明确类和接口的定义的目的,理解面向对象编程。
顺便强调一下,过多的if-else是糟糕的代码,凸显出程序的笨重,例如demo中,我们可以利用开关来解决:
/**
* 在基类中增加boolean型开关,并增加参数为ControllerButton的方法
*/
public class Switch {
...
public void excuteCommand(ControllerButton controllerButton) {
// 如果是开机功能,要发送记忆命令
if (controllerButton.isNeedMemory()) {
memoryCommand.onCommand();
}
excuteCommand(controllerButton);
}
public void excuteCommand(Command command) {
// 指示灯闪闪
lampBulingCommand.onCommand();
// 执行参数命令
command.onCommand();
}
}
最后要说的是,不要随意违背开发的基本原则,例如上面的那位朋友,这些规则是前辈经过长时间的研究总结的结晶,当然这些经验不一定是对的,也不适于所有的场景,但是如果你非要这么做,请做好充足的准备。