0.参考资料
1.概述
- 将一个请求(行为)封装为一个对象,从而使你可用不同的请求对客户进行参数化; 对请求排队或记录请求日志,以及支持可撤销的操作。 —《设计模式》GoF
- 命令模式(Command Pattern):在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计
1.1动机
- 在软件构建过程中,“行为请求者” 与“行为实现者”通常呈现一种 "紧耦合"。但在某些场合 -- 比如需要对行为进行"记录、撤销/重做(undo/redo)、事务”等处理,这种无法抵御变化的紧耦合是不合适的。
- 在这种情况下,如何将"行为请求者”与“行为实现者”解耦?将一组行为抽象为对象,可以实现二者之间的松耦合。
1.2结构
- ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1629882724266-48734264-6305-4fff-a8cf-9599f41a5a6b.png#clientId=u7075f660-2c7e-4&from=paste&height=225&id=u8ece9412&margin=%5Bobject%20Object%5D&name=image.png&originHeight=450&originWidth=1214&originalType=binary&ratio=1&size=184752&status=done&style=none&taskId=u29650f68-8cfe-4862-8999-ad6284e01e9&width=607)
- ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1630145304044-b5609c05-3675-4912-8e62-ca741a4d6da6.png#clientId=uac1adc34-039b-4&from=paste&height=210&id=u27f2a38b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=280&originWidth=567&originalType=binary&ratio=1&size=14961&status=done&style=none&taskId=u3fe98bfb-4c09-4e54-a408-b1a27974981&width=425)
- 对原理类图的说明-即(命名模式的角色及职责)
- 1) Invoker 是调用者角色
- 2) Command: 是命令角色,需要执行的所有命令都在这里,可以是接口或抽象类
- 3) Receiver: 接受者角色,知道如何实施和执行一个请求相关的操作
- 4) ConcreteCommand: 将一个接受者对象与一个动作绑定,调用接受者相应的操作,实现execute
2.要点总结
宏观架构
1. Command模式的根本目的在于将“行为请求者”与“行为实现者”解耦, 在面向对象语言中,常见的实现手段是”将行为抽象为对象”
1. 实现Command接口的具体命令对象ConcreteCommand有时候根据需要可能会保存一些额外的状态信息。 通过使用Composite模式 ,可以将多个”命令”封装为一个个"复合命令" MacroCommand.
1. Command模式与C+ +中的函数对象有些类似。但两者定义行为接口的规范有所区别: Command以面向对象中的"接口-实现” 来定义行为接口规范,更严格,但有性能损失; C+ +函数对象以函数签名来定义行为接口规范,更灵活,性能更高。
微观代码
1. 将发起请求的对象与执行请求的对象解耦。发起请求的对象是调用者,调用者只要
调用命令对象的execute()方法就可以让接收者工作,而不必知道具体的接收者对 象是谁、是如何实现的,命令对象会负责让接收者执行请求的动作,也就是说:” 请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到 了纽带桥梁的作用。
1. 可以设计一个命令队列。只要把命令对象放到列队,就可以多线程的执行命令
1. 方便实现对请求的撤销和重做
1. **命令模式不足:**可能导致某些系统有过多的具体命令类,增加了系统的复杂度,这
点在在使用的时候要注意
1. 空命令也是一种设计模式,它为我们省去了判空的操作。在上面的实例中,如果没
有用空命令,我们每按下一个按键都要判空,这给我们编码带来一定的麻烦。
1. 命令模式经典的应用场景:界面的一个按钮都是一条命令、模拟CMD(DOS命令) 订单的撤销/恢复、触发-反馈机制
3.案例
需求
1. 我们买了一套智能家电,有照明灯、风扇、冰箱、洗衣机,我们只要在手机上安装app就可以控制对这些家电工作。
1. 这些智能家电来自不同的厂家,我们不想针对每一种家电都安装一个App,分别控制,我们希望只要一个app就可以控制全部智能家电。
1. 要实现一个app控制所有智能家电的需要,则每个智能家电厂家都要提供一个统一的接口给app调用
分析
4.使用模式
方案
- ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1630145463524-a464c6a0-b35e-4fb2-910e-89e3ae3ce65f.png#clientId=u938b4e9e-05d4-4&from=paste&height=243&id=u1680866f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=323&originWidth=230&originalType=binary&ratio=1&size=13140&status=done&style=none&taskId=u139498e6-4020-4d9e-bd9f-3d60b85cef5&width=173)
类图
- ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1630145521690-6129199e-4307-4e25-903c-83a800dbf6c3.png#clientId=u938b4e9e-05d4-4&from=paste&height=242&id=u7cd63d57&margin=%5Bobject%20Object%5D&name=image.png&originHeight=484&originWidth=1113&originalType=binary&ratio=1&size=247496&status=done&style=none&taskId=u2a018237-c582-4ff1-bc1f-719dcd664e4&width=556.5)
- ![Command.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1630145625892-6e201e4a-c95c-4a3f-8edf-fa571b974e7c.png#clientId=u938b4e9e-05d4-4&from=ui&id=ub9f4620a&margin=%5Bobject%20Object%5D&name=Command.png&originHeight=922&originWidth=2476&originalType=binary&ratio=1&size=70400&status=done&style=none&taskId=uf55de3dd-31f9-459f-a9a3-bd266ca5e40)
代码
- Command
// 命令接口
public interface Command {
// 执行命令
void execute();
// 撤销命令
void undo();
}
// part1 的 on 命令
public class ConcreteCommand_part1_on implements Command {
Part1 part1;
public ConcreteCommand_part1_on(Part1 part1){
this.part1 = part1;
}
@Override
public void execute() {
part1.on();
}
@Override
public void undo() {
part1.off();
}
}
// part1 的 off 命令
public class ConcreteCommand_part1_off implements Command {
Part1 part1;
public ConcreteCommand_part1_off(Part1 part1){
this.part1 = part1;
}
@Override
public void execute() {
part1.off();
}
@Override
public void undo() {
part1.on();
}
}
// part2....
public class ConcreteCommand_part2_on implements Command {
Part2 part2;
public ConcreteCommand_part2_on(Part2 part2){
this.part2 = part2;
}
@Override
public void execute() {
part2.on();
}
@Override
public void undo() {
part2.off();
}
}
public class ConcreteCommand_part2_off implements Command {
Part2 part2;
public ConcreteCommand_part2_off(Part2 part2){
this.part2 = part2;
}
@Override
public void execute() {
part2.off();
}
@Override
public void undo() {
part2.on();
}
}
- 接收者
// 可忽略的开关接口
public interface Openable {
void on();
void off();
}
public class Part1 implements Openable {
@Override
public void on() {
System.out.println("part1 开");
}
@Override
public void off() {
System.out.println("part1 关");
}
}
public class Part2 implements Openable {
@Override
public void on() {
System.out.println("part2 开");
}
@Override
public void off() {
System.out.println("part2 关");
}
}
- 调用者
public class MyInvoke {
// 两条集合管理所有开关按钮
List<Command> onCommands = new ArrayList<>(10);
List<Command> offCommands = new ArrayList<>(10);
// 记录的可撤销一次
Command undoCommand;
// 添加组件
public void addCommand(int index, Command onCommand, Command offCommand){
onCommands.add(index, onCommand);
offCommands.add(index, offCommand);
}
// 按下某组件的开按钮,
public void onButtonForIndex(int index){
onCommands.get(index).execute();
// 记录操作, 以撤销
undoCommand = onCommands.get(index);
}
// 按下某组件的关按钮,
public void offButtonForIndex(int index){
offCommands.get(index).execute();
// 记录操作, 以撤销
undoCommand = offCommands.get(index);
}
// 撤销操作
public void undo(){
undoCommand.undo();
}
}
- 测试
public class Client {
public static void main(String[] args) {
// 初始化一个(空)遥控器
MyInvoke invoke = new MyInvoke();
// 创建接收者一号: part1, 二号
Part1 part1 = new Part1();
Part2 part2 = new Part2();
// 创建接收者对应的开关命令
Command part1_on_cmd = new ConcreteCommand_part1_on(part1);
Command part1_off_cmd = new ConcreteCommand_part1_off(part1);
Command part2_on_cmd = new ConcreteCommand_part2_on(part2);
Command part2_off_cmd = new ConcreteCommand_part2_off(part2);
// 开关命令填充进遥控器
invoke.addCommand(0, part1_on_cmd, part1_off_cmd);
invoke.addCommand(1, part2_on_cmd, part2_off_cmd);
// 使用测试
System.out.println("\t按下 part1 的 on:");
invoke.onButtonForIndex(0);
System.out.println("\t按下 part1 的 off:");
invoke.offButtonForIndex(0);
System.out.println("\t 撤回:...");
invoke.undo();
System.out.println("\t按下 part2 的 on:");
invoke.onButtonForIndex(1);
System.out.println("\t按下 part2 的 off:");
invoke.offButtonForIndex(1);
System.out.println("\t 撤回:...");
invoke.undo();
}
}
按下 part1 的 on:
part1 开
按下 part1 的 off:
part1 关
撤回:...
part1 开
按下 part2 的 on:
part2 开
按下 part2 的 off:
part2 关
撤回:...
part2 开
5.经典使用
5.1Spring中JdbcTemplate
说明
- ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1630146789653-3861dc19-31cd-40dc-a913-fa46d74c2b91.png#clientId=u69226290-61b9-4&from=paste&height=236&id=u9bf4ad65&margin=%5Bobject%20Object%5D&name=image.png&originHeight=472&originWidth=1144&originalType=binary&ratio=1&size=672838&status=done&style=none&taskId=u8c0794cb-a6c3-4851-9349-dfe86f25857&width=572)
分析
- StatementCallback 接口, 命令接口(Command). 其中 doInStatement(...) 即命令方法
- class QueryStatementCallback implements StatementCallback, SqlProvider , 匿名内<br />部类, 实现了命令接口. 调用真正的执行方法,真正的执行方法即stmt的executeQuery()。
- JdbcTemplate即充当调用者角色,又充当接收者角色。
- execute(StatementCallback action) ,调用者的调用方法. 调用了 (具体的的)action.doInStatement 方法.