首页 > 代码库 > 设计模式(6)--命令模式
设计模式(6)--命令模式
关键词 :空对象 有人称为设计模式
三层调用
1. 封装调用 , 把封装带到一个全新的境界: 把方法调用(method invocation) 封装起来.
2. 命令模式可将"动作的请求者" 从"动作的执行者" 对象中解耦.
3. 当需要将发出的请求和执行请求的对象解耦的时候,使用命令模式.
OO原则: (1)封装变化 (2) 多用组合,少用继承 (3)针对接口编程,不针对实现编程 (4)为交互对象之间松耦合设计而努力
(5) 类应该对扩展开放,对修改关闭 (6) 依赖抽象,不要依赖具体类.
OO模式: 命令模式-- 将请求封装成对象,这可以让你使用不同的请求,队列,或者日志请求来参数化其它对象.命令模式也支持撤销操作.
调用者(遥控器) 命令 设置命令 命令接收者(电灯) 执行
//设计遥控器的API . 开 关 撤消
(1).实现命令接口
public interface Command{
public void execute();
}
(2).实现一个打开电灯的命令
public class LightOnCommand implements Command{ //这是一个命令所以需要实现Command接口.
Light light;
public LightOnCommand(Light light){
this.light = light; //构造器传入某个电灯 一旦调用execute()就由电灯成为接收者负责接受请求
}
public void execute(){
light.on(); //这个execute()方法调用接收对象(现在是电灯)的on()方法.
}
} //现在有了LightOnCommand 如何应用?
(3).有一个遥控器,只有一个按钮和一个对应的插槽,可以控制一个装置
public class SimpleRemoteControl {
Command slot; //有一个插槽持有命令,
public SimpleRemoteControl(){}
public void setCommand(Command command){
slot = command; //这里设置插槽行为的命令
}
public void buttonWasPressed(){
slot.execute(); // 调用 实现Command接口类的 execute (里面是持有对象的动作如 light.on());
}
}
(4). 遥控器使用的简单测试
public class RemoteControlTest{ //命令模式的客户
public static void main(String[] args){
SimpleREmoteControl remote = new SimpleRemoteControl(); //遥控器的调用者
Light light = new Light();
LightOnCommand lightOn = new LightOnCommand(light); //light 是命令的接收者
remote.setCommand(lightOn); //把命令传给调用者
remote.buttonWasPressed(); //按下按钮.
}
}
-------第二个例子
7个插槽的遥控器
public class RemoteControl {
Command[] onCommands; //开的命令
Command[] offCommands ; //关的命令
public RemoteControl(){
onCommands = new Command[7]; //初始化7个按钮
offCommands = new Command[7];
Command noCommand = new NoCommand(); //这里用到了空对象
for(int i =0; i<7;i++){
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
}
public void setCommand(int slot, Command onCommand,Command offCommand){
onCommands[slot]= onCommand; //slot 位置
offCommands[slot]= offCommand;
}
public void onButtonWasPushed(int slot){
onCommands[slot] .execute();
}
public void offButtonWasPushed(int slot){
offCommands[slot] .execute();
}
}
空对象问题
有遥控器中,我们不想每次都检查是否某个插槽都加载了命令. 比方说, 在这个OnButtonWasPressed()方法中,可能需要这样的代码
public void OnButtonWasPushed(int slot){
if(onCommands[slot] !=null){ //用了空对象 这里就可以省略.
onCommands[slot].execute();
}
}
public class NoCommand implements Command{
public void exectue(){}
}
//有 RemoteControl 的构造器中,为每个插槽预先指定NoCommand对象, 以便确定每个插槽永远都有命令对象
Command noCommand = new NoCommand();
for(int i =0;i<7;i++){
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
模式荣誉奖
NoCommand 对象是一个空对象(null object)的例子. 当你不想返回一个有意义的对象时,空对象就很有用.客户也可将处理null的责任转移给空对象. 举例来说,遥控器不可能一出厂就设置了有意义的对象,所以提供noCommand 对象作为代用品,当调用它的execute()方法时,这种对象什么事情也不做
在许多设计模式中,都会看到空对象的使用.甚至有些时个,空对象本身也被视为一种设计模式.
------撤消按钮 功能 (记住上一命令)
要加上撤消按钮的支持.加上一个新的实例变量,用来追踪最后被调用的命令. 然后调用其undo()方法.
public class RemoteControlWithUndo{
Command[] onCommands;
Command[] offCommands;
Command undoCommand; //前一个命令将记录在这里
public RemoteControlWithUndo(){
onCommands = new Command[7];
offCommands = new Command[7];
Command noCommand = new NoCommand();
for(int i=0;i<7;i++){
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
undoCommand = noCommand; //一开始前没有 所谓的 "前一个命令"
}
public void setCommand(int slot,Command onCommand, Command offCommand){
onCommand[slot] =onCommand;
offCommand[slot] = offCommand;
}
public void OnButtonWasPushed(int slot){
onCommands[slot].execute();
undoCommand = onCommands[slot]; //记住 该按钮
}
public void offButtonWasPushed(int slot){
offCommands[slot].execute();
undoCommand = offCommands[slot]; //记住 该按钮
}
public void undoButtonWasPushed(){
undoCommand.undo(); //按下撤消按钮时
}
}
-------每个遥控器都需具备"Party模式" (按下一个按钮执行多个命令) 宏命令.
public class MacroCommand implements Command{
Command[] commands;
public MacroCommand(Command[] commands){ //单个命令时这里接收的是对象接收者如(light)
this.commands = commands; //在宏命令中,用命令数组存储一大堆命令
}
public void execute(){
for(int i =0; i<commands.length;i++){
commands [i].execute(); //依次执行命令数组中的命令
}
}
public void undo(){
for(int i =0; i<commands.length;i++){
commands[i]. undo // 因为持久的命令对象如: LightOnCommand 所以可以直接撤销(调用的是其
LightOnComand .undo () 方 法)
}
}
}
Command[] partyOn = {lightOn,stereoOn, tvOn,hobutton};
MacroCommand partyOnMacro = new MacroCommand(partyOn);
remoteControl .setCommand(0,partyOnMacro,partyOffMacro ); //将命令数组给一个按钮.
问: 我如何实现多层撤消操作?
答: 使用一个堆栈记录操作过程的每个命令,然后,不管什么时候按下撤销按纽,都可以从堆栈中取出最上层命令.
命令模式的更多用途:日志请求
有些应用需要将所有的命令记录在日志中,前能在系统死机后,重新调用动作恢复之前的状态. 在Java中,我们可以利用对象的序列化(Serialization)实现该方法, 但是一般认为序列化最好还是只用在对象的持久化上(persistence).
要点:
(1). 命令模式将发出请求的对象和执行请求的对象解耦
(2). 在被解耦的两者之间是通过命令对象沟通的,命令对象封装了接收者的一个或一组动作
(3). 调用者通过调用命令对象的execute()发出请求,这会使用接收者的动作被调用
(4). 调用者可以接受命令当参数,甚至在运行时动态的进行.
(5). 命令可以支持撤消,做法是实现一个undo()方法来回到execute()被执行前的状态.
(6). 宏命令是命令的一种简单的延伸,允许调用多个命令.宏方法也支持撤销.
(7). 实际操作时,很常见使用"聪明" 命令对象,也就是直接实现了请求而不是将工作委托给接收者
(8). 命令也可以用来实现日志和事务系统 .