设计模式——命令模式
命令模式定义
将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。
命令模式可将“动作的请求者”从“动作的执行者”对象中解耦。
这个模型允许将“发出请求的对象”和“接受与执行这些请求的对象”分隔开来。
命令模式的类图:
类图角色:
Client:负责创建一个具体的命令对象ConcreteCommand,并设置这个命令对象的接收者。创建调用者对象,并在调用者对象上调用setCommand()方法,传入命令对象,把命令对象存储在其中,以后需要用到。
**Receiver:**接收者,任何类都可以充当。接收者知道如何进行必要的工作。
ConcreteCommand:这个命令对象定义了动作和接收者之间的绑定关系。调用者调用命令对象的execute()方法发出请求。然后由命令对象调用接收者的一个或多个动作响应请求。
Command:为所有命令对象定义了一个接口。所有命令对象都必须实现这个接口。调用命令对象的execute方法就可以让接收者进行相关的动作。调用undo方法就可以撤销该动作。
Invoker:调用者持有命令对象,并在某个时间点调用命令对象的execute方法,将请求负诸实行。
命令模式的优势就是允许将“发出请求的对象“与”接受与执行这些请求的对象“分隔开来。比如说对于遥控器API,我们需要分隔开”发出请求的按钮代码“和“执行请求的特定对象”。遥控器的每个插槽都对应一个命令,那么当按下一个按钮时,相应的命令对象的execute方法就会被调用,其结果就是接收者动作被调用。遥控器不需要知道事情是怎么发生的,也不需要知道涉及哪些对象。
当接受与执行这些请求的对象(即接收者)越复杂,涉及到的其他对象越多,命令模式的优势越是明显。
下面我们就用遥控器控制电灯的开与关来举例说明命令模式:
1.定义一个接收者,此处就是电灯,:
package target;
public class Light { public void on(){ System.out.println("light is on"); } public void off(){ System.out.println("light is off"); }
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
2.定义一个命令接口,所有的命令对象都必须实现这个接口:
package inter;
public interface Command { void execute();//执行动作 void undo();//撤销该动作
}
- 1
- 2
- 3
- 4
- 5
- 6
3.定义两个具体的命令对象:开命令对象、关命令对象:
开命令对象:对开命令对象而言,execute执行的是开动作,那么undo方法就是撤销该动作,即恢复关的状态。
package impl;
import inter.Command;
import receiver.Light;
public class LightOnCommand implements Command { private Light light; public LightOnCommand(Light light){ this.light = light; } @Override public void execute() { light.on(); } @Override public void undo() { light.off(); }
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
关命令对象:对关命令对象而言,execute执行的是关动作,那么undo方法就是撤销该动作,即恢复开的状态。
package impl;
import inter.Command;
import receiver.Light;
public class LightOffCommand implements Command { private Light light; public LightOffCommand(Light light){ this.light = light; } @Override public void execute() { light.off(); } @Override public void undo() { light.on(); }
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
我们最好再定义一个默认的命令对象,这个对象什么都不做:
package impl;
import inter.Command;
public class NoCommand implements Command { @Override public void execute() { } @Override public void undo() { }
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
4.调用者对象
package invoker;
import impl.NoCommand;
import inter.Command;
public class RemoteController { private Command[] onCommands; //按钮很多,因此设置成数组,开命令对象数组 private Command[] offCommands;//关命令对象数组 private Command undoCommand;//前一个命令将被记录在此 ,用于做撤销用的 public RemoteController(){ onCommands = new Command[3]; offCommands = new Command[3]; for(int i = 0 ;i < 3; i++){ onCommands[i] = new NoCommand(); offCommands[i] = new NoCommand(); } undoCommand = new NoCommand(); } //传入命令对象,把命令对象存储在其中,以后需要用到 public void setCommand(int slot,Command onCommand,Command offCommand){ if(slot < 0 || slot >= 3) return; onCommands[slot] = onCommand; offCommands[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(); }
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
5.测试
import impl.LightOffCommand;
import impl.LightOnCommand;
import inter.Command;
import invoker.RemoteController;
import receiver.Light;
public class Main { public static void main(String[] args) { Light light = new Light(); Command onCommand = new LightOnCommand(light); Command offCommand = new LightOffCommand(light); RemoteController remoteController = new RemoteController(); remoteController.setCommand(0,onCommand,offCommand); remoteController.onButtonWasPushed(0); //开灯 remoteController.undoButtonWasPushed();//撤销 remoteController.offButtonWasPushed(0); //关灯 remoteController.undoButtonWasPushed();//撤销 }
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
6.测试结果
light is on
light is off
light is off
light is on
- 1
- 2
- 3
- 4
命令模式的更多用途
1.队列请求
如有这样一个队列,我们可以在某一端添加命令,然后另一端是线程。线程进行下面的动作:从队列中取出一个命令,调用它的execute方法,等待这个调用完成,然后将此命令对象丢弃,再取出下一个命令,直到队列为空。这个过程中,工作队列类和进行计算的对象之间完全是解耦的,此刻线程可能在做着复杂的计算,或者可能正在读取网络数据等。
工作队列对象不在乎到底做些什么,它们只知道取出命令对象,然后调用其execute方法。类似地,它们只要是实现命令模式的对象,就可以放入队列里,当线程可用时,就调用此对象的execute方法。
2.日志请求
某些应用需要我们把所有的动作都记录在日志里,并能在系统死机后,重新调用这些动作恢复到之前的状态。通过新增两个方法(store()、load()),命令模式就能够支持这一点。在Java中,我们可以利用对象的序列化(Serialization)实现这些方法,但是一般认为序列化最好还是用在对象的持久化上。
当我们执行命令时,交历史记录储存在磁盘中。一旦死机,我们就可以将命令对象重新加载,并成批地依次地调用这些对象的execute方法。对许多调用大型数据结构的动作的应用无法在每次改变发生时被快速地存储。通过使用记录日志,我们可以将上次检查点之后的所有操作记录下来,如果系统出状况,从检查点开始应用这些操作。比方说,对于电子表格应用,我们可能想要实现的错误恢复方式是将电子表格的操作记录在日志中,而不是每次电子表格一有变化就记录整个电子表格。对更高级的应用而言,这些技巧可以被扩展应用到事务处理中,也就是说,一整群操作必须全部进行完成,或者没有进行任何的操作。
小结
- 命令模式将发出请求的对象和执行请求的对象解耦。
- 在被解耦的两者之间是通过命令对象进行沟通的。命令对象封装了接收者和一个或一组动作。
- 调用者通过调用命令对象的execute()方法发出请求,这会使得接收者的动作被调用。
- 调用者可以接受命令当做参数,甚至在运行时动态地进行。
- 命令可以支持撤销,做法是实现一个undo()方法来回到execute()被执行前的状态。
- 宏命令是命令的一种简单的延伸,允许调用多个命令。宏方法也可以支持撤销。
- 命令也可以用来实现日志和事务系统。
谢谢阅读。
文章来源: blog.csdn.net,作者:WongKyunban,版权归原作者所有,如需转载,请联系作者。
原文链接:blog.csdn.net/weixin_40763897/article/details/88874391
- 点赞
- 收藏
- 关注作者
评论(0)