封装“方法调用”,将“发出请求的对象”和“接收与执行这些请求的对象”分隔开来。
命令模式
将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。
生活中的命令模式——餐厅点餐
① 你,也就是顾客,把订单交给女招待;
②女招待拿了订单,放在订单柜台,然后喊了一声“订单来了”;
③快餐厨师根据订单准备餐点。
思考:对象和方法的调用关系
①顾客知道他要的是什么,他创建一张订单 createOrder();
②女招待拿走了订单 takeOrder(),放在订单柜台,然后调用 orderUp() 方法,通知厨师准备点餐;
③快餐厨师根据指令准备餐点。
继续分析(文字有点多,请耐心阅读)
①一张订单封装了准备餐点的请求
把订单想象成一个用来请求准备餐点的对象,和一般的对象一样,订单对象可以被传递:从女招待传递到订单柜台,或者从女招待传递到接替下一班的女招待。
订单的接口包含一个方法,也就是 orderUp()。这个方法封装了准备餐点所需的动作。订单内有一个到“需要进行准备工作的对象”(也就是厨师)的引用。
这一切都被封装起来,所以女招待不需要知道订单上有什么,也不需要知道是谁来准备餐点;她只需要将订单放到订单窗口,然后喊一声“订单来了”就可以了。
②女招待的工作是接收订单,然后调用订单的orderUp()方法。
女招待的工作很简单:接下顾客的订单,继续帮助下一个顾客,然后将一定数量的订单放到订单柜台,并调用orderUp()方法,让人来准备餐点。她不必担心订单的内容是什么,或者由谁来准备餐点。她只需要知道,订单有一个orderUp()方法可以调用,这就够了。
不同的顾客有不同的订单,这会使得女招待的takeOrder()方法被传入不同的参数,女招待知道所有的订单都支持orderUp()方法,任何时候他需要准备餐点是,调用这个方法就是了。
③快餐厨师具备准备餐点的知识。
一旦女招待调用orderUp()方法,快餐厨师就接手,厨师具有创建餐点的所有方法。
请注意,女招待和厨师之间是彻底解耦的:女招待的订单封装了餐点的细节,她只要调用每个订单的方法即可,而厨师看了订单就知道该做些什么餐点;厨师和女招待之间从来不需要直接沟通。
命令模式类图
呃。。。上面的类图还是有点难看懂,没事,完成下面的案例后,再回头看看这个类图,你就清晰了。
需求
你们公司需要开发一套系统,运用于家电控制的遥控器。这个遥控器有4个按钮,两排两列,分别是对应于控制灯的开关,车库门的开关,另外还有一个撤销按钮(undo)。
类似下图:
①实现命令接口——Command
首先,让所有的命令对象实现相同的包含一个方法的接口。在餐厅的例子中,我们称此方法为orderUp(),然而,现在改为一般惯用的名称excute().
public interface Command {
/**
* 执行
*/
void excute();
/**
* 撤销
*/
void undo();
}
②具体的命令
开灯命令——LightOnCommand
public class LightOnCommand implements Command {
private Light mLight;
public LightOnCommand(Light light) {
mLight = light;
}
@Override
public void excute() {
mLight.on();
}
@Override
public void undo() {
mLight.off();
}
}
关灯命令——LightOffCommand
public class LightOffCommand implements Command {
private Light mLight;
public LightOffCommand(Light light) {
mLight = light;
}
@Override
public void excute() {
mLight.off();
}
@Override
public void undo() {
mLight.on();
}
}
开车库门命令——GarageDoorUpCommand
public class GarageDoorUpCommand implements Command {
private GarageDoor mGarageDoor;
public GarageDoorUpCommand(GarageDoor garageDoor) {
mGarageDoor = garageDoor;
}
@Override
public void excute() {
mGarageDoor.up();
}
@Override
public void undo() {
mGarageDoor.down();
}
}
关车库门命令——GarageDoorDownCommand
public class GarageDoorDownCommand implements Command {
private GarageDoor mGarageDoor;
public GarageDoorDownCommand(GarageDoor garageDoor) {
mGarageDoor = garageDoor;
}
@Override
public void excute() {
mGarageDoor.down();
}
@Override
public void undo() {
mGarageDoor.up();
}
}
③遥控器 控制类——RemoteControlWithUndo
public class RemoteControlWithUndo {
private List mOnCommands = new ArrayList<>();//开
private List mOffCommands = new ArrayList<>();//关
private Command mUndoCommand;//撤销
public RemoteControlWithUndo() {
NoCommand noCommand = new NoCommand();
for (int i = 0; i < 2; i++) {
mOnCommands.add(noCommand);
mOffCommands.add(noCommand);
}
// 一开始,并没有所谓的“前一个命令”,所以将它设置为NoCommand的对象。
mUndoCommand = noCommand;
}
/**
* 给按钮设置命令
*
* @param slot 位置,列
* @param onCommand 开命令
* @param offCommand 关命令
*/
public void setCommand(int slot, Command onCommand, Command offCommand) {
mOnCommands.set(slot, onCommand);
mOffCommands.set(slot, offCommand);
}
/**
* 点击某一列的 开 按钮
*
* @param slot 位置,列
*/
public void onButtonWasPushed(int slot) {
mOnCommands.get(slot).excute();
mUndoCommand = mOnCommands.get(slot);
}
/**
* 点击某一列的 关 按钮
*
* @param slot 位置,列
*/
public void offButtonWasPushed(int slot) {
mOffCommands.get(slot).excute();
mUndoCommand = mOffCommands.get(slot);
}
/**
* 撤销,返回前一个命令 的操作
*/
public void undoButtonWasPushed() {
mUndoCommand.undo();
}
}
在遥控器中我们不想每次都检查是否某个按钮下面都加载了命令,比方说,在onButtonWasPushed()方法中,我们可能需要这样的代码
public void onButtonWasPushed(int slot) {
if (mOnCommands.get(slot) != null) {
mOnCommands.get(slot).excute();
mUndoCommand = mOnCommands.get(slot);
}
}
所以我们在RemoteControlWithUndo的构造器中,每个按钮先指定成NoCommand对象,以便每个按钮永远都有命令对象。
NoCommand noCommand = new NoCommand();
for (int i = 0; i < 2; i++) {
mOnCommands.add(noCommand);
mOffCommands.add(noCommand);
}
// 一开始,并没有所谓的“前一个命令”,所以将它设置为NoCommand的对象。
mUndoCommand = noCommand;
NoCommand对象
public class NoCommand implements Command {
@Override
public void excute() {
System.out.println("No Command");
}
@Override
public void undo() {
System.out.println("No Command");
}
}
④测试
public class TestCommand {
public static void main(String args[]) {
RemoteControlWithUndo remoteControlWithUndo = new RemoteControlWithUndo();
Light light = new Light();//灯
LightOnCommand lightOnCommand = new LightOnCommand(light);//开灯命令
LightOffCommand lightOffCommand = new LightOffCommand(light);//关灯命令
remoteControlWithUndo.setCommand(0, lightOnCommand, lightOffCommand);//将命令设置到调用者中
GarageDoor garageDoor = new GarageDoor();//车库门
GarageDoorUpCommand garageDoorUpCommand = new GarageDoorUpCommand(garageDoor);//开车库门命令
GarageDoorDownCommand garageDoorDownCommand = new GarageDoorDownCommand(garageDoor);//关车库门命令
remoteControlWithUndo.setCommand(1, garageDoorUpCommand, garageDoorDownCommand);//
remoteControlWithUndo.onButtonWasPushed(0);
remoteControlWithUndo.offButtonWasPushed(0);
remoteControlWithUndo.onButtonWasPushed(1);
remoteControlWithUndo.offButtonWasPushed(1);
remoteControlWithUndo.undoButtonWasPushed();//撤销,返回上一个命令
}
}
结果
Light is On
Light is Off
GarageDoor is Up
GarageDoor is down
GarageDoor is Up
小知识
NoCommand对象是一个空对象(null object)的例子。当你不想返回一个有意义的对象时,空对象就很有用。客户也可以将处理null的责任转移给空对象。举例来说,遥控器不可能一出场就设置了有意义的命令对象,所以提供了NoCommand对象作为代用品,当调用它的excute()方法时,这种对象什么事情都不做。
在许多设计模式中,都会看到空对象的使用。甚至有些时候,空对象本身也被视为一种设计模式。
现在,你可以回过头去看看上面的 命令模式的类图,看看是否对命令模式更加理解了。
要点
- 命令模式将 发出请求的对象 和 执行请求的对象 解耦。
- 在被解耦的两者之间是通过命令对象进行沟通的。命令对象封装了接收者和一个或一组动作。
- 调用者通过调用命令对象的excute()发出请求,这会使得接收者的动作被调用。
- 命令可以支持撤销,做法是实现一个undo()方法来回到excute()被执行前的状态。
感谢你的耐心阅读,命令模式基本知识和应用就介绍到这里了。
最后回顾一下
我们的设计工具箱中的工具
1、OO基础
① 抽象
② 封装
③ 多态
④ 继承
2、OO原则
① 封装变化
② 多用组合,少用继承
③ 针对接口编程,不针对实现编程
④ 为交互对象之间的松耦合设计而努力
⑤ 类应该对扩展开放,对修改关闭
⑥ 依赖抽象,不要依赖具体编程
3、OO模式
① 策略模式——定义算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
② 观察者模式——在对象之间定义了一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象都会受到通知并自动更新。
③ 装饰者模式——动态地将责任附加到对象上,若要扩展功能,装饰者提供有别于继承的另一种选择。
④ 工厂方法模式——定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
抽象工厂模式——提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
⑤单件模式——确保一个类只有一个实例,并提供全局访问点。
⑥命令模式——将“请求”封装成对象,这可以让你使用不同的请求、队列或者日志来参数化其他对象。命令模式也可以支持撤销操作。
感谢阅读!
No.5 单件模式(singleton )
No.4 工厂模式(Factory)
No.3 装饰者模式(Decorator)
No.2 观察者模式(Observer)
No.1 策略模式(Strategy)
前言 为何要使用设计模式
Demo代码