《Head-First-设计模式》笔记8-命令模式
定义
命令模式将“请求”封装成对象,以便使用不同的请求,队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。
在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计,使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活。
命令模式可以对发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。这就是命令模式的模式动机。
常见的情景
在餐厅点餐工作流程中,顾客将写好的订单看做一个命令,服务员接受了这个命令,然后他把订单(命令)交给厨师,厨师再根据订单(命令)去做菜。在这个情景中,顾客是动作请求者,厨师是动作执行者,二者并没有接触(解耦),但是通过命令的传递完成了整个点菜流程。
模式结构
命令模式包含如下角色:
- Command: 抽象命令类
- ConcreteCommand: 具体命令类
- Invoker: 调用者
- Receiver: 接收者
- Client:客户类
从餐厅到命令模式
对应关系:
- 女招待————Invoker
- 厨师————Receiver
- 顾客————Client
- 订单————Command
- orderUp()————execute()
- takeOrder()————setComand()
简单实例代码
命令接口
public interface Command {
public void execute();
}
电灯及打开电灯命令
// 电灯
public class Light {
public void on() {
System.out.println("on");
}
public void off() {
System.out.println("off");
}
}
// 打开电灯命令
public class LightOnCommand implements Command {
Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.on();
}
}
使用命令对象
public class SimpleRemoteControl {
Command slot;
public void setCommand(Command command) {
slot = command;
}
public void buttonWasPressed() {
slot.execute();
}
}
测试
public class RemoteControTest {
public static void main(String[] args) {
SimpleRemoteControl remote = new SimpleRemoteControl();
Light light = new Light();
LightOnCommand lightOn = new LightOnCommand(light);
remote.setCommand(lightOn);
remote.buttonWasPressed();
}
} /*output:
on
*/
支持撤销,多个命令实例代码
命令接口
public interface Command {
public void execute();
public void undo(); //增加撤回功能
}
打开电灯命令
电灯类不变
public class LightOnCommand implements Command {
Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.on();
}
@Override
public void undo() {
System.out.println("undo");
light.off();
}
}
关灯命令
public class LightOffCommand implements Command {
Light light;
public LightOffCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.off();
}
@Override
public void undo() {
System.out.println("undo");
light.on();
}
}
NoCommand
NoCommand 对象是一个空对象(null object)的例子。当你不想返回一个有意义的对象时,空对象就很有用。客户也可以将处理 null 的责任转移给空对象。举例来说,遥控器不可能一出厂就设置了有意义的命令对象,所以提供了 Nocommand 对象作为代用品,当调用它的 execute() 方法时这种对象什么事情都不做。
在许多设计模式中,都会看到空对象的使用。甚至有些时候,空对象本身也被视为是一种设计模式。
public class NoCommand implements Command {
@Override
public void execute() { }
@Override
public void undo() { }
}
使用命令对象(遥控器)
public class RemoteControl {
Command[] onCommands; // 多个命令
Command[] offCommands;
Command undoCommand;
public RemoteControl() {
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) {
onCommands[slot] = onCommand;
offCommands[slot] = offCommand;
}
public void onButtonWasPressed(int slot) {
onCommands[slot].execute();
undoCommand = onCommands[slot];
}
public void offButtonWasPressed(int slot) {
offCommands[slot].execute();
undoCommand = offCommands[slot];
}
public void undoButtonWasPressed() {
undoCommand.undo();
}
}
风扇(多种状态)
风速不同,多种设置。
public class CeilingFan {
public static final int HIGH = 3;
public static final int MEDIUM = 2;
public static final int LOW = 1;
public static final int OFF = 0;
int speed;
public CeilingFan() { speed = OFF; }
public void high() { speed = HIGH; }
public void medium() { speed = MEDIUM; }
public void low() { speed = LOW; }
public void off() { speed = OFF; }
public int getSpeed() { return speed; }
}
风扇高档命令
风扇其他命令省略。
public class CeilingFanHighCommand implements Command {
CeilingFan ceilingFan;
int prevSpeed;
public CeilingFanHighCommand(CeilingFan ceilingFan) {
this.ceilingFan = ceilingFan;
}
@Override
public void execute() {
prevSpeed = ceilingFan.getSpeed();
}
@Override
public void undo() {
if (prevSpeed == CeilingFan.HIGH) {
ceilingFan.high();
} else if (prevSpeed == CeilingFan.MEDIUM) {
ceilingFan.medium();
} else if (prevSpeed == CeilingFan.LOW) {
ceilingFan.low();
} else if (prevSpeed == CeilingFan.OFF) {
ceilingFan.off();
}
}
}
多个命令
测试时可以使用两个 Command[],一个存放 on 命令,另一个存放 off 命令。可同时执行多个命令。
public class MacroCommand implements Command {
Command[] commands;
public MacroCommand(Command[] commands) {
this.commands = commands;
}
@Override
public void execute() {
for (int i = 0; i < commands.length; i++) {
commands[i].execute();
}
}
@Override
public void undo() {
for (int i = 0; i < commands.length; i++) {
commands[i].undo();
}
}
}
测试
public class RemoteControTest {
public static void main(String[] args) {
RemoteControl remote = new RemoteControl();
Light light = new Light();
LightOnCommand lightOn = new LightOnCommand(light);
LightOffCommand lightOff = new LightOffCommand(light);
remote.setCommand(0, lightOn, lightOff);
remote.onButtonWasPressed(0);
remote.offButtonWasPressed(0);
remote.undoButtonWasPressed();
}
}/*output:
on
off
undo
on
*/
命令模式的其他用途
- 队列请求:有一个工作队列:你在某端添加命令,然后另一端是线程。线程进行下面的动作:从队列中取出一个命令,调用它的 execute() 方法,等待这个调用完成,然后将此命令对象丢弃,再取出下一个命令;
- 日志请求: 某些应用需要我们将所有的动作都记录到 日志中, 并能在系统死机后,重新调用这些动作恢复到之前的状态。how to do that? 当我们执行命令的时候,将历史记录存储在磁盘中。一旦系统死机,我们就可以将命令对象重新加载,并成批次地调用这些对象的 execute 方法;
优缺点
命令模式的优点
- 降低系统的耦合度。
- 新的命令可以很容易地加入到系统中。
- 可以比较容易地设计一个命令队列和宏命令(组合命令)。
- 可以方便地实现对请求的 Undo 和 Redo。
命令模式的缺点
- 使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 bin07280@qq.com
文章标题:《Head-First-设计模式》笔记8-命令模式
文章字数:1.5k
本文作者:Bin
发布时间:2018-07-11, 20:18:55
最后更新:2019-08-06, 00:07:35
原始链接:http://coolview.github.io/2018/07/11/Head-First-%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E3%80%8AHead-First-%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E3%80%8B%E7%AC%94%E8%AE%B08-%E5%91%BD%E4%BB%A4%E6%A8%A1%E5%BC%8F/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。