文章目录
- 模式介绍
- 优缺点
- 适用场景
- 结构
- 案例实现
- 注意事项
模式介绍
有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。此时希望用一种松耦合的方式来设计程序,使得请求发送者和请求接收者能够消除彼此之间的耦合关系。
拿订餐来说,客人需要向厨师发送请求,但是完全不知道这些厨师的名字和联系方式,也不知道厨师炒菜的方式和步骤。 命令模式把客人订餐的请求封装成 command
对象,也就是订餐中的订单对象。这个对象可以在程序中被四处传递,就像订单可以从服务员手中传到厨师的手中。这样一来,客人不需要知道厨师的名字,从而解开了请求调用者和请求接收者之间的耦合关系。
模式定义:
命令模式是一个行为模式,它是将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行存储、传递、调用、增加与管理。
优缺点
优点:
- 类间解耦: 调用者角色与接收者角色之间没有任何依赖关系,调用者实现功能时只需调用
Command
抽象类的execute
方法就可以,不需要了解到底是哪个接收者执行。 - 可扩展性:
Command
的子类可以非常容易地扩展,而调用者Invoker
和高层次的模块Client
不产生严 重的代码耦合。 - 命令模式结合其他模式会更优秀: 命令模式可以结合责任链模式,实现命令族解析任务;结合模板方法模式,则可以减少
Command
子类的膨胀问题。
缺点:
- 如果有N个命令,
Command
的子类就可不是几个,而是N
个,这个类膨胀得非常大。
适用场景
- 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
- 系统需要在不同的时间指定请求、将请求排队和执行请求。
- 系统需要支持命令的撤销(
Undo
)操作和恢复(Redo
)操作。
结构
命令模式包含以下主要角色:
- 抽象命令类(
Command
)角色: 定义命令的接口,声明执行的方法。 - 具体命令(
Concrete Command
)角色: 具体的命令,实现命令接口;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。 - 实现者/接收者(
Receiver
)角色: 接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。 - 调用者/请求者(
Invoker
)角色: 要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
案例实现
以生活中的开灯关灯为例: 在小丽家装了智能家居以后,老王迫不及待的体验了一下遥控关灯开灯功能,这可比手动抹黑找开关方便多了,我们先来分析命令模式的角色在该案例中由谁来充当。
遥控器: 就是调用者角色,由她来发起命令(持有命令)。
电灯: 就是接收者角色,真正命令执行的对象(被命令持有)。
UML类图如下:
抽象命令类:
public interface Command {
void execute();
}
开灯,关灯命令【具体命令类】:
public class LightOffCommand implements Command{
private LightReceiver lightReceiver;
public LightOffCommand(LightReceiver lightReceiver) {
this.lightReceiver = lightReceiver;
}
@Override
public void execute() {
lightReceiver.off();
}
}
public class LightOnCommand implements Command{
private LightReceiver lightReceiver;
public LightOnCommand(LightReceiver lightReceiver) {
this.lightReceiver = lightReceiver;
}
@Override
public void execute() {
lightReceiver.on();
}
}
电灯【接收者角色】:
public class LightReceiver {
public void on(){
System.out.println("电灯打开了...");
}
public void off(){
System.out.println("电灯关闭了...");
}
}
遥控器【请求者角色】:
public class RemoteController {
private Command command;
//添加命令
public void setCommand(Command command) {
this.command = command;
}
public void executeCommand(){
if (command != null){
command.execute();
}
}
}
测试:
public class Client {
public static void main(String[] args) {
//遥控器(请求者)
RemoteController robotInvoker = new RemoteController();
//电灯(接收者)
LightReceiver lightReceiver = new LightReceiver();
//开灯
LightOnCommand lightOnCommand = new LightOnCommand(lightReceiver);
robotInvoker.setCommand(lightOnCommand);
robotInvoker.executeCommand();
//关灯
LightOffCommand lightOffCommand = new LightOffCommand(lightReceiver);
robotInvoker.setCommand(lightOffCommand);
robotInvoker.executeCommand();
}
}
注意事项
Command
接口非常简单,通常只有一个execute
方法,如果要支持撤销操作的话,再加一个unexecute
方法;- 每个具体的命令类内部封装了实际执行命令的那个类(
Recevier
),或者那些类,以及执行需要的数据; - 每个具体命令类只完成一个请求,有多少个请求就有多少个命令;
Invoker
类只认识接口Command
,其他的都不认识;- 客户端类负责生成命令,并通过
Invoker
组装执行。