OOP设计模式[JAVA]——04命令模式
命令模式
命令模式的意图
命令模式属于对象的行为模式。别名又叫:Action或Transaction。
命令模式把一个请求或者操作封装到一个对象中。命令模式允许系统使用不同的请求把客户端参数化,对请求排队或者记录日志,可以提供命令的撤销和恢复功能。
命令模式的结构
命令模式的参与者
- Command
——声明一个给所有具体命令类的抽象接口。这是一个抽象角色,通常由一个Java接口或Java抽象类实现。
- ConcreteCommand
——将一个接收者对象绑定于一个动作
——调用接收者相应的操作,以实现execute
- Client
——创建一个具体命令对象并设定它的接收者
- Invoker
——负责调用命令对象执行请求。
- Receiver
——知道如何实施与执行一个请求相关的操作。任何类都可能作为一个接收者。
命令模式的活动序列
- 客户端创建了一个ConcreteCommand对象,并指明了接收者。
- 请求者对象保存了ConcreteCommand对象。
- 请求者对象通过调用action()方法发出请求。如果命令是撤消的,那么ConcreteCommand保存了调用execute()方法之前的状态。
- ConcreteCommand对象调用接收的一方的方法执行请求。
命令模式的使用场景
家电遥控器,该遥控器的原型如下:
- 有7个可编程的插槽(每一个都可以指定到一个不同的家电装置)
- 每个插槽都有对应的开关按钮
- 具备一个整体的撤销按键
下面我们来看看最简单的实现。
Command:抽象命令接口
/** * 抽象接口 Command * @author nick * */ public interface Command { public void execute(); //非常简单,只需要一个方法:execute() }
ConcreteCommand:具体命令类
/** * 具体命令类 * @author nick * */ public class LightOnCommand implements Command { //构造器传入了接收对象,以便让这个命令控制,然后记录在实例变量中。 //一旦调用execute()方法,就由这个对象成为接收者,负责接收请求 public LightOnCommand(Light light) { super(); this.light = light; } @Override public void execute() { // 这个execute()方法调用接收对象的on()方法 light.on(); } Light light; }
Client:客户端
/** * 遥控器测试类------>客户端 * @author nick * */ public class RemoteControlTest { /** * @param args */ public static void main(String[] args) { // 遥控器就是调用者,会传入一个命令对象,可以用来发出请求 SimpleRemoteControl remote = new SimpleRemoteControl(); //创建一个电灯对象,此对象就是请求的接收者 Light light = new Light(); //创建一个命令,然后将接收者传递给它 LightOnCommand lightOn = new LightOnCommand(light); remote.setCommand(lightOn); //把命令传给调用者 remote.buttonWasPressed(); //模拟遥控器的按钮按下动作 } }
invoker:调用者
/** * 遥控器类------>调用者 * * @author nick */ public class SimpleRemoteControl { Command slot; //有一个插槽持有命令,而这个命令控制着一个装置 public SimpleRemoteControl() { } public void setCommand(Command command) { //用来设置插槽控制的命令 slot = command; } public void buttonWasPressed() { //遥控器按钮按下时,使得当前命令衔接插槽 slot.execute(); } }
Receiver:接收者
/** * 电灯对象------>接收者 * @author nick * */ public class Light { int level; public Light() {} public void on() { level = 100; System.out.println("Light is on"); } public void off() { level = 0; System.out.println("Light is off"); } public void dim(int level) { this.level = level; if (level == 0) { off(); } else { System.out.println("Light is dimmed to " + level + "%"); } } public int getLevel() { return level; } }
模拟结果输出如下:
Light is on
以上只是单个命令的操作,如果我们需要添加一个LightOff的命令呢,那该怎么办?看下面:
/** * 具体命令类---->关闭电灯 * @author nick * */ public class LightOffCommand implements Command { //构造器传入了接收对象,以便让这个命令控制,然后记录在实例变量中。 //一旦调用execute()方法,就由这个对象成为接收者,负责接收请求 public LightOffCommand(Light light) { super(); this.light = light; } @Override public void execute() { // 这个execute()方法调用接收对象的off()方法 light.off(); } Light light; }
/** * 遥控器测试类------>客户端 * @author nick * */ public class RemoteControlTest { /** * @param args */ public static void main(String[] args) { // 遥控器就是调用者,会传入一个命令对象,可以用来发出请求 SimpleRemoteControl remote = new SimpleRemoteControl(); //创建一个电灯对象,此对象就是请求的接收者 Light light = new Light(); //创建一个命令,然后将接收者传递给它 LightOnCommand lightOn = new LightOnCommand(light); LightOffCommand lightOff = new LightOffCommand(light); //添加LightOff命令 remote.setCommand(lightOn); //把命令传给调用者 remote.setCommand(lightOff); remote.buttonWasPressed(); //模拟遥控器的按钮按下动作 } }
好了,其他几个控制命令,可以按这个去添加即可。但是我们别忘了,Command模式不是说还可以提供撤消和恢复功能吗,那这个功能我们又该如何实现呢?
因为每个控制按钮都需要实现撤消功能,所以我们要在抽象命令的接口中定义该撤消功能的方法:
/** * 抽象接口 Command * @author nick * */ public interface Command { public void execute(); // 非常简单,只需要一个方法:execute() public void undo(); // 添加一个撤消方法 }
具体命令类的实现:
/** * 具体命令类 * @author nick * */ public class LightOnCommand implements Command { //构造器传入了接收对象,以便让这个命令控制,然后记录在实例变量中。 //一旦调用execute()方法,就由这个对象成为接收者,负责接收请求 public LightOnCommand(Light light) { super(); this.light = light; } @Override public void execute() { // 这个execute()方法调用接收对象的on()方法 light.on(); } @Override public void undo() { // 实现抽象命令中的撤消功能,该撤消动作就是电灯对象的上一个动作(light off) System.out.println("Ready to undo---->"); light.off(); } Light light; }
/** * 具体命令类---->关闭电灯 * @author nick * */ public class LightOffCommand implements Command { //构造器传入了接收对象,以便让这个命令控制,然后记录在实例变量中。 //一旦调用execute()方法,就由这个对象成为接收者,负责接收请求 public LightOffCommand(Light light) { super(); this.light = light; } @Override public void execute() { // 这个execute()方法调用接收对象的off()方法 light.off(); } public void undo() { // 实现抽象命令中的撤消功能,该撤消动作就是电灯对象的上一个动作(light on) System.out.println("Ready to undo---->"); light.on(); } Light light; }
调用者类
/** * 遥控器类------>调用者 * * @author nick */ public class SimpleRemoteControl { Command onCommand; Command offCommand; Command undoCommand; public SimpleRemoteControl() { } public void setCommand(Command onCom, Command offCom) {//用来设置插槽控制的命令 onCommand = onCom; offCommand = offCom; } public void onButtonWasPressed() { //开电灯按钮按下,执行打开电灯命令 onCommand.execute(); undoCommand = onCommand; } public void offButtonWasPressed() { //关电灯按钮按下,执行关闭电灯命令 offCommand.execute(); undoCommand = offCommand; } public void undoButtonWasPressed() { //撤消按钮按下,执行撤消动作 undoCommand.undo(); } }
客户端
/** * 遥控器测试类------>客户端 * @author nick * */ public class RemoteControlTest { /** * @param args */ public static void main(String[] args) { // 遥控器就是调用者,会传入一个命令对象,可以用来发出请求 SimpleRemoteControl remote = new SimpleRemoteControl(); //创建一个电灯对象,此对象就是请求的接收者 Light light = new Light(); //创建一个命令,然后将接收者传递给它 LightOnCommand lightOn = new LightOnCommand(light); LightOffCommand lightOff = new LightOffCommand(light); //添加LightOff命令 remote.setCommand(lightOn, lightOff); remote.onButtonWasPressed(); //模拟遥控器的按钮按下动作 remote.undoButtonWasPressed(); System.out.println("++++++++++++++++++++"); remote.offButtonWasPressed(); remote.undoButtonWasPressed(); } }
测试结果如下:
Light is on
Ready to undo---->
Light is off
++++++++++++++++++++
Light is off
Ready to undo---->
Light is on
命令模式的效果
Command模式有以下效果:
- Command模式将调用操作的对象与知道如何实现该操作的对象解耦
- Command是头等的对象。它们可像其他的对象一样被操作和扩展
- 可将多个命令装配成一个复合命令。
- 增加新的Command很容易,无需改变已有的类
命令模式的实现
实现command模式须考虑的问题有如下:
- 一个命令对象应达到何种智能程度 命令对象的能力可大可小。一个极端是它仅确定一个接收者和执行该请求的动作。另一个极端是它自己实现所有功能,根本不需要接收对象。当需要定义与已有的类无关的命令,当没有合适的接收者,或当一个命令隐式地知道它的接收者时,可以使用后一极端方式。在这两个极端间的情况是命令对象有足够的信息可以动态的找到它们的接收者。
- 支持取消(undo)和重做(redo) 如果Command提供方法逆转它们操作的执行(undo),就可支持取消和重做功能。为达到这个目的,ConcreteCommand类可能需要存储额外的状态信息。这个状态包括:
- 接收者对象,它真正执行处理该请求的各操作。
- 接收者上执行操作的参数
- 如果处理请求的操作会改变接收者对象中的某些值,那么这些值也必须先存储起来。接收者还必须提供一些操作,以使该命令可将接收者恢复到它先前的状态。
若应用只支持一次取消操作,那么只需存储最近一次被执行的命令。而若要支持多级的取消和重做,就需要有一个已被执行命令的历史表列(history list),该表列的最大长度决定了取消和重做的级数。历史表列存储了已被执行的命令序列。向后遍历该表列并逆向执行( r e v e r s e - e x e c u t i n g )命令是取消它们的结果;向前遍历并执行命令是重执行它们。有时可能不得不将一个可撤消的命令在它可以被放入历史列表中之前先拷贝下来。这是因为执行原来的请求的命令对象将在稍后执行其他的请求。如果命令的状态在各次调用之间会发生变化,那就必须进行拷贝以区分相同命令的不同调用。
- 避免取消操作过程中的错误积累