Java设计模式(十一)
-----------命令模式Command
引入目的
为了完成调用者和接受者之间的解耦,简略调用者端的代码设计,使得将来对调用者代码维护成本大大降低。
例如:调用者要增加一个控制功能,我们只需要编写新的Command,在调用者添加该命令的引用,然后在客户端指定好Command的具体接受者,并且为调用者setCommand,就可以了。
定义与组成
定义
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。
角色
我们用一个遥控器的例子来阐述这个模式中的每一个对象。
话说有一个神奇的遥控器,上面有很多按钮,每个按钮都控制着一个家用电器,遥控器并不用知道这些家用电器具体是怎么运转的,它只需要发出命令(按动相应按钮)即可。
好了,下面我们注意分析以下模式中的角色
抽象命令
定义了excute()和undo()方法,当然如果有需要还可以增加redo…一切需要的方法。
public interface Command { public void excute();
public void undo();
} |
具体命令
命令对象将接收者和具体对象封装成对象,定义了接受者和动作的绑定关系,实现了抽象命令中定义的接口,这些接口实现调用了绑定接受者的具体行为。
public class OpenWaterHeaderCommand implements Command { private WaterHeater wh; public OpenWaterHeaderCommand(WaterHeater wh) { this.wh = wh; } public void excute() { this.wh.open(); } public void undo() { this.wh.close(); } }
|
public class PlayDVDCommand implements Command { private DVD dvd; public PlayDVDCommand(DVD dvd) { this.dvd = dvd; } public void excute() { dvd.play(); } public void undo() { dvd.stop(); } }
|
public class CloseTVCommand implements Command { private TV tv; public CloseTVCommand(TV tv) { this.tv = tv; } public void excute() { tv.close(); } public void undo() { tv.open(); } } |
接收者
真正接受动作的实例,在本例中我们定义 热水器,电视机,DVD机三个类
public class DVD { public void open() { System.out.println("打开DVD"); } public void close() { System.out.println("关闭DVD"); } public void play() { System.out.println("DVD开始播放"); } public void stop() { System.out.println("DVD停止播放"); } }
|
public class TV { public void open() { System.out.println("打开电视机"); }
public void close() { System.out.println("关闭电视机"); }
}
|
public class WaterHeater {
public void open() { System.out.println("打开热水器"); }
public void close() { System.out.println("关闭热水器"); }
}
|
调用者
存储命令对象实例,实现setCommand方法
public class RemoteController { OpenWaterHeaderCommand openWHCommand; PlayDVDCommand playDVDCommand; CloseTVCommand closeTVCommand;
private Command curCommand;
public void clickOpenWaterHeaderButton() { openWHCommand.excute(); curCommand = openWHCommand; }
public void clickCloseTVButton() { closeTVCommand.excute(); curCommand = closeTVCommand; }
public void clickPlayDVDButton() { playDVDCommand.excute(); curCommand = playDVDCommand; }
public void setOpenWHCommand(OpenWaterHeaderCommand openWHCommand) { this.openWHCommand = openWHCommand; }
public void setPlayDVDCommand(PlayDVDCommand playDVDCommand) { this.playDVDCommand = playDVDCommand; }
public void setCloseTVCommand(CloseTVCommand closeTVCommand) { this.closeTVCommand = closeTVCommand; }
public void clickUndoButton() { curCommand.undo(); } } |
客户端
拥有调用者实例和接受者实例,可以创建命令实例,并且指定其接受者,调用setCommand方法。我们可以把Command理解成调用者和接受者的桥梁。
public class MyTest {
/** * @param args * 我们可以理解成自己(一个人)或者就是你的业务逻辑 */ public static void main(String[] args) { // TODO Auto-generated method stub //接收者 WaterHeater wh=new WaterHeater(); TV tv=new TV(); DVD dvd=new DVD();
//命令,指定接收者 OpenWaterHeaderCommand c1=new OpenWaterHeaderCommand(wh); PlayDVDCommand c2=new PlayDVDCommand(dvd); CloseTVCommand c3=new CloseTVCommand(tv);
//实例化调用者 RemoteController control=new RemoteController(); control.setOpenWHCommand(c1); control.setCloseTVCommand(c3); control.setPlayDVDCommand(c2);
//按动按钮 control.clickCloseTVButton(); control.clickOpenWaterHeaderButton();
control.clickPlayDVDButton(); control.clickUndoButton(); } }
调用结果: 关闭电视机 打开热水器 DVD开始播放 DVD停止播放
|
下图就是命令模式条用关系的一个直观总结!
模式使用流程
1.客户创建一个命令对象
2.客户用setCommand将命令对象存储在调用者中(遥控器)
3.在某个时刻,客户可以要求调用者调用这个命令。一旦命令被加载到调用者,该命令可以被加载或丢弃,也可以存储下来多次使用。
4. 如果要实现undo操作,那么就要在Command接口中增加undo方法,然后在调用者中记录最后一个执行的Command对对象。如果遇到带有多种状态的撤销操作,那么我们在该Command的excute执行时,首先要记录下这个实例变化前的状态,在undo方法中去判断后调用接受者不同的action。
总结
优点
1) 命令模式将调用操作的请求对象与知道如何实现该操作的接收对象解耦。
2) 具体命令角色可以被不同的请求者角色重用。
3) 你可将多个命令装配成一个复合命令。
4) 增加新的具体命令角色很容易,因为这无需改变已有的类。
适用环境
1) 需要抽象出待执行的动作,然后以参数的形式提供出来——类似于过程设计中的回调机
制。而命令模式正是回调机制的一个面向对象的替代品。
2) 在不同的时刻指定、排列和执行请求。一个命令对象可以有与初始请求无关的生存期。
3) 需要支持取消操作。
4) 支持修改日志功能。这样当系统崩溃时,这些修改可以被重做一遍。
5) 需要支持事务操作。