一.场景
在餐厅里,我们要吃饭,就需要对餐厅发出请求,此时,我们是请求发出者。餐厅的厨师则是请求实现者,如果我们直接和厨师交流沟通说自己想吃什么,这时,我们和厨师就是一种强耦合关系,当顾客越来越多,厨师也各司其职的时候,就不好运作了。
解决:通过点餐的菜单来对我们进行解藕,我们把想吃的菜在菜单上勾选,交给服务员,服务员交给厨师,实际上是服务员告诉厨师,调用他的烹饪方法,最后把做好的菜也就是结果返回给我们,这时我们之间是解藕的关系,服务员不会关心我们在菜单上写了什么,厨师也不用和顾客交流。
二.命令模式介绍
定义
命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。
使用场景
在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。当我们需要对请求发出者和请求实现者进行解藕时,使用命令模式。
三.类图结构和实现
1.类图
2.解释
- 命令(Command):为所有命令声明了一个接口。调用命令对象的 execute()方法,就可以让接收者进行相关的操作。
- 具体命令(ConcreteCommand):实现命令接口,定义了动作和接收者之间的绑定关系。调用者只要调用 execute() 就可以发出请求,然后由 ConcreteCommand 调用接收者的一个或多个动作。
- 请求者(Invoker):持有一个命令对象,有一个行动方法,在某个时间点调用命令对象的 execute() 方法,将请求付诸实行
- 接收者(Receiver):接收者知道如何进行必要的动作,实现这个请求。任何类都可以当接收者
- 客户端(Client):创建一个具体命令(ConcreteCommand)对象并确定其接收者,包括把其他角色串连在一起。
3.和例子对应
- 服务员 对应Command 它可以调用命令对象的execute()方法
- 厨师 execute() 负责做菜
- 菜单 Invoker 请求者 持有命令对象
- 顾客 Receiver 接受者 就是最后做菜给的人
- Client 对应谁???
Client是创建命令对象,并确定接受者的角色,在点餐环节,Client其实可以看作是餐厅的接客流程,它需要创建命令对象,将命令对象存储在Invoker中。
四.代码例子:
注意,这个例子选自《Head First 设计模式》一书,并非本人原创。遥控器控制开关灯的例子
1.定义Command
Command很简单,就是一个接口
public interface Command {
/**
* 执行命令
*/
void execute();
}
2.定义接受者,相当于点餐的顾客
这个类负责显示结果给我们看
public class Light {
public void on() {
System.out.println("灯亮了...");
}
}
3.实现一个打开电灯的命令
这个类实现了Command接口,属于ConcreteCommand
public class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.on();
}
}
4.构建请求者 遥控器
请求者持有一个命令对象,有一个行动方法
public class RemoteInvoker {
private Command undoCommand;
/**
* 开关命令数组,模拟有很多对开关数组
*/
private Command[] onCommands;
public RemoteInvoker(int length) {
// 有几组开关,就设置多少数组
onCommands = new Command[length];
// 把每个命令初始化成空命令,避免空指针异常
Command noCommand = new NoCommand();
undoCommand = noCommand;
for (int i = 0; i < length; i++) {
onCommands[i] = noCommand;
}
}
/**
* @Description 设置命令对象
* @param slot 遥控器的位置
* @param onCommand 开的命令
* @return void
*/
public void setCommond(int slot, Command onCommand, Command offCommand) {
onCommands[slot] = onCommand;
}
public void onButton(int slot) {
onCommands[slot].execute();
}
}
5.定义Client把他们串起来
public class RemoteClient {
public static void main(String[] args) {
// 1、创建接收者
Light light = new Light();
// 2、创建命令对象
LightOnCommand lightOnCommand = new LightOnCommand(light);
// 3、创建一组开关并用命令对象装载它
RemoteInvoker invoker = new RemoteInvoker(1);
invoker.setCommond(0, lightOnCommand);
// 4、测试
invoker.onButton(0);
}
}
5.优点和缺点
优点:
- 降低了系统耦合度。
- 新的命令可以很容易添加到系统中去。
缺点:
- 使用命令模式可能会导致某些系统有过多的具体命令类。