命令模式
定义
命令模式
将请求
封装成对象,以便使用不同的请求、队列或日志来参数化其他对象。命令模式也支持可撤销的操作。一个命令对象通过在特定接收者上绑定一组动作来封装一个请求。要达到这一点,命令对象将动作和接收者包进对象。这个对象只暴露出一个
execute()
方法,当此方法被调用的时候,接收者就会进行这些动作。从外面看,其他对象不知道空间哪个接收者进行了哪些动作,只知道如果调用了execute()
方法,请求的目的就能达到。
类图
实现
1. 定义Command接口
/**
* 命令定义
*/
public interface Command {
/**
* 执行命令
*/
void execute();
/**
* 回退命令
*/
void undo();
}
2. 定义命令接口实现类
/**
* 包装开灯、关灯指令
* ConcreteCommand
*/
public class LightOffCommand implements Command {
Light light;
public LightOffCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.off();
}
@Override
public void undo() {
light.on();
}
}
/**
* 包装开灯、关灯指令
* ConcreteCommand
*/
public class LightOnCommand implements Command {
Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.on();
}
@Override
public void undo() {
light.off();
}
}
3.定义命令接收者
/**
* Receiver
* 具体指令执行者
*/
public class Light {
public void on() {
System.out.println("Light ON.");
}
public void off() {
System.out.println("Light OFF.");
}
}
4. 定义调用者
/**
* 使用命令对象
* 假设只有一个遥控器,它只有一个按钮和对应的插槽,可以控制一个装置。
* Invoker
*/
public class RemoteControl {
// 有一个插槽持有命令,而这个命令控制着一个着墨
private Command[] onCommands;
private Command[] offCommands;
private 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 onComand, Command offCommand) {
onCommands[slot] = onComand;
offCommands[slot] = offCommand;
}
/**
* 当按下按钮时,这个方法就会被调用。使得当前命令衔接插槽,并调用它的`execute()`方法
*/
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();
System.out.println("UNDO");
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("\n-------- Remote Control ---------\n");
for (int i = 0; i < onCommands.length; i++) {
if (onCommands[i] == null) {
continue;
}
sb.append("[slot " + i + "] " + onCommands[i].getClass().getName() + " " + offCommands[i].getClass().getName() + "\n");
}
return sb.toString();
}
}
5. 定义Client
public class M {
/**
* M 为命令模式的客户。Client
*/
public static void main(String[] args) {
// 遥控器就是调用者,会传入一个命令对象,可以用来发出请求
RemoteControl remoteControl = new RemoteControl();
// 现在创建了一个电灯对象,此对象也是请求的接收者
Light light = new Light();
// 创建一个命令,并将接收者传递给命令
LightOnCommand lightOnCommand = new LightOnCommand(light);
LightOffCommand lightOffCommand = new LightOffCommand(light);
// 模拟按下按钮
// 在简单的遥控器中,我们先用一个 `打开电灯`命令加载按钮插槽,后来替换为`打开车库电灯`命令。
// 遥控器插槽并根本不在乎所拥有的是什么命令对象。只要该命令对象实现了`command`接口就可以。
GarageDoor garageDoor = new GarageDoor();
GarageDoorOnCommand garageDoorOnCommand = new GarageDoorOnCommand(garageDoor);
GarageDoorOffCommand garageDoorOffCommand = new GarageDoorOffCommand(garageDoor);
remoteControl.setCommand(0, lightOnCommand, lightOffCommand);
remoteControl.setCommand(1, garageDoorOnCommand, garageDoorOffCommand);
remoteControl.onButtonWasPushed(0);
remoteControl.onButtonWasPushed(1);
remoteControl.offButtonWasPushed(0);
remoteControl.undoButtonWasPushed();
remoteControl.offButtonWasPushed(1);
}
}
带状态回滚
public class CeilingFanOffCommand implements Command {
CeilingFan ceilingFan;
// 增加局部状态变量,以便追踪吊扇之前的速度
int prevSpeed;
public CeilingFanOffCommand(CeilingFan ceilingFan) {
this.ceilingFan = ceilingFan;
}
@Override
public void execute() {
// 先将它的状态记录下来,以便需要撤销的时候用到
prevSpeed = ceilingFan.getSpeed();
ceilingFan.off();
}
@Override
public void undo() {
if (prevSpeed == 3) {
ceilingFan.high();
} else if (prevSpeed == 2) {
ceilingFan.medium();
} else if (prevSpeed == 1) {
ceilingFan.low();
} else {
ceilingFan.off();
}
}
}
总结
- 命令模式可以将
动作的请求者
从动作的执行者
对象中解耦。
- 遥控器应该知道如何解读按钮被按下的动作,然后发出正确的请求。但是不知道这些家电自动化的细节。
- 使用状态实现撤销。
- 命令模式更多用途: 队列请求。命令可以将运算块打包(一个接收者和一组动作),然后将它传来传去。就像是一般的对象一样。利用这样的特性衍生出: 日程安排(Scheduler)、线程池、工作队列。
- 在某一端添加命令,然后另一端则是线程。线程进行如下操作: 从队列中取出一个命令,调用它的execute()方法,等待这个调用完成,然后将此命令对象丢弃,再取出下一个命令。
-
每个设计模式都应该由两部分组成: 一是
应用场景
、二是解决方案
。如果只关注代码实现,会产生大部分设计模式看起来很相似的感觉。实际上,设计模式之间的主要区别在于设计意图,即应用场景。 -
命令模式
VS策略模式
- 策略模式包含策略的定义、创建和使用三部分。从代码角度看,非常类似于
工厂模式
。区别在于,策略模式侧重“策略”或“算法”这个特定的应用场景,用来解决根据运行时状态从一组策略中选择不同策略的问题,而工厂模式侧重封装对象的创建过程,这里的对象没有任何业务场景的限定,可以是策略,但也可以是其他东西。从设计意图上来,这两个模式完全是两回事儿。 - 命令模式的主要作用和应用场景,是用来控制命令的执行,比如,异步、延迟、排队执行命令、撤销重做命令、存储命令、给命令记录日志等等,这才是命令模式能发挥独一无二作用的地方。
- 策略模式包含策略的定义、创建和使用三部分。从代码角度看,非常类似于
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。