序
不知道是不是因为看书状态不好,书上的例子确实总是有种云里雾里的感觉,不过幸好有程序,看程序基本上就明白这个所谓的命令模式大概是怎么回事了。
1、背景
为了简化说明,书中的例子将在此处简化。现在个遥控器,共有14个按钮,左边7个表示“开”,右边7个表示“关”。这样,每一对开关可以对应一种家用电器。当按下开的时候,对应的家用电器启动。当按下关的时候,家用电器关闭。
如果到这来说,其实就很简单。可以将每对按钮设定为一个类,类中存有开启和关闭某个家用电器的操作。用一个集合对象来存储这7组按钮即可。但是,问题来了。如果原来风扇扔掉了,换成了空调——也就是控制的家用电器的种类发生了变化,或者说有一些家电不用了,或者更极端些,希望一组按钮能开启、关闭所有家电——总之,遥控器的功能要发生改变,怎么办?
首先确认的是,为了保证更好的封装特性,不能修改遥控器类。那么据此,我们引入当前的模式:命令模式。
2、命令模式
命令模式将:发出命令的控制实体(遥控器上的按钮),接受命令的实体(遥控器上发出的不同的红外线光),执行命令的功能实体(家用电器)三者进行剥离。
接受命令的实体相当于遥控器与家电的一个通用接口,或者说是一个管道。管道一头连接着发出命令的实体,为控制实体提供一个统一的发出命令的接口。管道另一头连接着执行命令的功能实体,为功能实体提供一个统一接收命令的接口。命令在这个管道上流通。
好吧,这么说还是很抽象,我觉得上程序应该更能有所体会。
public class Test2 {
public static void main(String argv[])
{
Light light=new Light();//定义电灯
Lighton lighton=new Lighton(light);//定义关于电灯开操作的接收实体
Lightoff lightoff=new Lightoff(light);//定义关于电灯关操作的接收实体
CeilingFan ceilingFan=new CeilingFan();
Ceilingfanon ceilingfanon=new Ceilingfanon(ceilingFan);
Ceilingfanoff ceilingfanoff=new Ceilingfanoff(ceilingFan);
CtlCommand con=new CtlCommand(2); //定义控制实体
con.setExecutors(0, lighton, lightoff); //将接收实体加入控制实体中
con.setExecutors(1, ceilingfanon, ceilingfanoff);
con.pushOnButton(0);//控制实体,通过接受实体,调用执行实体的功能
con.pushOnButton(1);
con.pushOffButton(0);
con.pushOffButton(1);
}
}
//发出命令的控制实体
class CtlCommand
{
private Executor[] onExecutors;
private Executor[] offExecutors;
public CtlCommand(int buttonAmount) //给出按钮
{
this.onExecutors=new Executor[buttonAmount];
this.offExecutors=new Executor[buttonAmount];
for (int i = 0; i < offExecutors.length; i++) {
this.onExecutors[i]=new Noexecute();//Noexecute为不会产生任何功能的功能实体。可以看作是虚拟的家用电器。
this.offExecutors[i]=new Noexecute();
}
}
public void setExecutors(int buttonNumber, Executor onExecutor, Executor offExecutor) {//给每个按钮配置接收实体
this.onExecutors[buttonNumber]=onExecutor;
this.offExecutors[buttonNumber]=offExecutor;
}
public void pushOnButton(int buttonNumber){//接收实体提供统一的execute()接口,调用功能实体的功能
this.onExecutors[buttonNumber].execute();
}
public void pushOffButton(int buttonNumber){
this.offExecutors[buttonNumber].execute();
}
}
//定义了2个家用电器
class Light//灯
{
public void on(){}
public void off(){}
}
class CeilingFan//吊扇
{
//风速
public void low(){}
public void medium(){}
public void high(){}
//开关
public void on(){}
public void off(){}
}
//定义接受命令实体的统一接口
interface Executor
{
public void execute();
}
//电灯接收实体
class Lighton implements Executor
{
Light light;
public Lighton(Light light) {this.light=light;}
public void execute() {this.light.on();}
}
class Lightoff implements Executor
{
Light light;
public Lightoff(Light light) {this.light=light;}
@Override
public void execute() {this.light.off();}
}
//电扇接受实体
class Ceilingfanon implements Executor
{
CeilingFan ceilingFan;
public Ceilingfanon(CeilingFan ceilingFan) {
this.ceilingFan=ceilingFan;
}
@Override
public void execute() {
this.ceilingFan.on();
this.ceilingFan.medium();
}
}
class Ceilingfanoff implements Executor
{
CeilingFan ceilingFan;
public Ceilingfanoff(CeilingFan ceilingFan) {
this.ceilingFan=ceilingFan;
}
@Override
public void execute() {
this.ceilingFan.off();
}
}
//空接收命令实体
class Noexecute implements Executor
{
@Override
public void execute() {}
}
不得不说,这个模式确实看起来很复杂,一方面涉及到的类和接口比较多。另一方面,每个类或者接口之间的关系也比较复杂。但是复杂并不代表一点规律都没有。下面将详细说明这些关系,以及其中蕴含的规律:
3、代码解析
(1)程序共有两个功能实体:Light和CeilingFan(此处简称灯和吊扇)。其中灯实体只有on()和off()方法,因此恰可以对应遥控器按钮的buttonOn和buttonOff。而吊扇实体中,由于还可以调节风速,因此规定,对于吊扇的buttonOn来说,不仅要把吊扇打开,还需要指定风速为medium()。吊扇的buttonOff则对应off()函数。
(2)由于一个按钮对应一个(或一组)操作,因此需要将每个操作,再次封装到一个类中。这个类就是接收实体。其使用了统一的Executor接口。真实由于这个原因,所以出现了LightOn类和LightOff类,CeilingFanon类和CeilingFanoff类。这四个类都实现了execute()方法,从而为控制实体提供了统一的调用接口。而具体的操作,则通过execute()方法进行封装。
同时,还需注意到,这些接收实体以组合方式,将功能实体包含进来。这也是第一步的解耦过程。因为同一类型的吊扇,可能又有很多个品牌。采用组合的方式,极大地放宽了吊扇的品种。
(3)而控制实体相当于一个大容器。他将所有的Executor接口对应的类型都聚集到这里,然后再用Executor提供的统一方法,对某个函数进行调用。这是第二步的解耦。控制实体只是判断方法——控制实体是方法的函数(此处用到了数学概念,数学思维确实真的很好用,尤其是分析问题的时候),而不是方法对应的某个对象的函数(即控制实体是execute()的函数,而不是execute()所属对象的函数)。
这样,便将控制实体、接受实体、功能实体进行了剥离。而每个实体,又有一点点联系——即他们的联系全都集中在execute()这个方法中。
功能实体为接受实体提供了execute()的内容。而接收实体为控制实体提供了execute()这张皮。——突然想到了月饼:
功能实体就好像馅料,接收实体就好像月饼皮,控制实体就好像食客。
4、继续工作
其实这个事还是没有说完,书上又进一步做了更深入的讨论:
1)遥控器上又提供了一个undo()撤回按钮。当按这个按钮的时候,上一个操作将被撤回。比如上一个操作为:开灯。那按一下undo()按钮,就能实现关灯的操作。
这个其实也很好做。还是看代码:
public class Test2 {
public static void main(String argv[])
{
Light light=new Light();//定义电灯
Lighton lighton=new Lighton(light);//定义关于电灯开操作的接收实体
Lightoff lightoff=new Lightoff(light);//定义关于电灯关操作的接收实体
CeilingFan ceilingFan=new CeilingFan();
Ceilingfanon ceilingfanon=new Ceilingfanon(ceilingFan);
Ceilingfanoff ceilingfanoff=new Ceilingfanoff(ceilingFan);
CtlCommand con=new CtlCommand(2); //定义控制实体
con.setExecutors(0, lighton, lightoff); //将接收实体加入控制实体中
con.setExecutors(1, ceilingfanon, ceilingfanoff);
con.pushOnButton(0);//控制实体,通过接受实体,调用执行实体的功能
con.pushOnButton(1);
con.pushOffButton(0);
con.pushOffButton(1);
}
}
//发出命令的控制实体
class CtlCommand
{
private Executor[] onExecutors;
private Executor[] offExecutors;
private Executor undoExecutor;//增加一个记录上次执行操作的属性
public CtlCommand(int buttonAmount) //给出按钮
{
this.onExecutors=new Executor[buttonAmount];
this.offExecutors=new Executor[buttonAmount];
this.undoExecutor=new Noexecute();//初始化一个undo
for (int i = 0; i < offExecutors.length; i++) {
this.onExecutors[i]=new Noexecute();//Noexecute为不会产生任何功能的功能实体。可以看作是虚拟的家用电器。
this.offExecutors[i]=new Noexecute();
}
}
public void setExecutors(int buttonNumber, Executor onExecutor, Executor offExecutor) {//给每个按钮配置接收实体
this.onExecutors[buttonNumber]=onExecutor;
this.offExecutors[buttonNumber]=offExecutor;
}
public void pushOnButton(int buttonNumber){//接收实体提供统一的execute()接口,调用功能实体的功能
this.onExecutors[buttonNumber].execute();
this.undoExecutor=this.onExecutors[buttonNumber];
}
public void pushOffButton(int buttonNumber){
this.offExecutors[buttonNumber].execute();
this.undoExecutor=this.offExecutors[buttonNumber];
}
public void pushUndoButton()//点击撤销按钮
{
this.undoExecutor.undo();
}
}
//定义了2个家用电器
class Light//灯
{
public void on(){}
public void off(){}
}
class CeilingFan//吊扇
{
//风速
public void low(){}
public void medium(){}
public void high(){}
//开关
public void on(){}
public void off(){}
}
//定义接受命令实体的统一接口
interface Executor
{
public void execute();
public void undo();//提供撤销操作接口
}
//电灯接收实体
class Lighton implements Executor
{
Light light;
public Lighton(Light light) {this.light=light;}
public void execute() {this.light.on();}
public void undo(){this.light.off();}
}
class Lightoff implements Executor
{
Light light;
public Lightoff(Light light) {this.light=light;}
@Override
public void execute() {this.light.off();}
public void undo(){this.light.on();}
}
//电扇接受实体
class Ceilingfanon implements Executor
{
CeilingFan ceilingFan;
public Ceilingfanon(CeilingFan ceilingFan) {
this.ceilingFan=ceilingFan;
}
@Override
public void execute() {
this.ceilingFan.on();
this.ceilingFan.medium();
}
public void undo(){this.ceilingFan.off();}
}
class Ceilingfanoff implements Executor
{
CeilingFan ceilingFan;
public Ceilingfanoff(CeilingFan ceilingFan) {
this.ceilingFan=ceilingFan;
}
@Override
public void execute() {
this.ceilingFan.off();
}
public void undo(){this.ceilingFan.off();}
}
//空接收命令实体
class Noexecute implements Executor
{
@Override
public void execute() {}
public void undo(){}
}
这个就不详细解析了。大家注意下四个位置(两个实体,一个接口)。
第一个位置:接口Executor中增加了undo接口。
第二个位置:所以,实现该接口的所有类,都给出了相对于execute()相反操作的undo()方法。
第三个位置:控制实体增加了一个属性,该属性记录上次操作对应的接收实体。
第四个位置:增加了按钮对应的方法pushUndoButton()。
2)对于像吊扇这种有风速控制的家电,是否可以根据其前一个状态恢复?
3)是否一个按钮可以控制一堆家用电器,比如同时开启电扇、电灯、电视这三个家用电器?
4)是否可以记录最后若干个操作,当多次按undo()按钮时,可以顺次进行操作的回退操作。比如操作序列:开电视、开灯、开电扇、开洗衣机。则回退操作序列为:关洗衣机、关电扇、关灯、关电视?
此处就不仔细说了。架构已经给出。:-P
网友评论