命令模式是一种行为设计模式,它将请求或操作封装成单个对象,并允许您使用不同的请求、队列或日志请求参数化或将其延迟。它使得对象能够在发出请求的对象和执行请求的对象之间进行解耦。
命令模式的结构包括四个主要部分:命令接口、具体命令、调用者和接收者。下面我们来详细介绍一下这四个部分的作用及其在命令模式中的实现。
1. 命令接口(Command Interface)
命令接口定义了命令对象的基本方法,包括执行命令的方法 execute()。在命令模式中,所有的命令都应该实现该接口。命令接口通常如下所示:
```
interface Command {
execute(): void;
}
```
2. 具体命令(Concrete Command)
具体命令是实现命令接口的具体类,它包含了一个接收者对象,并在调用 execute() 方法时,调用接收者的一个或多个方法来完成请求。具体命令通常包含一个构造函数,用于接收接收者对象,并将其存储在成员变量中。
```
class ConcreteCommand implements Command {
private receiver: Receiver;
constructor(receiver: Receiver) {
this.receiver = receiver;
}
execute(): void {
this.receiver.action();
}
}
```
3. 调用者(Invoker)
调用者是一个包含命令对象的类,它只知道调用命令对象的 execute() 方法,而不需要了解命令对象实际执行的操作。调用者通常包含一个 setCommand() 方法,用于设置命令对象,并在需要时调用该对象的 execute() 方法。
```
class Invoker {
private command: Command;
setCommand(command: Command): void {
this.command = command;
}
executeCommand(): void {
this.command.execute();
}
}
```
4. 接收者(Receiver)
接收者是实际执行命令操作的对象。在命令模式中,每个具体命令都需要与一个接收者相关联。接收者通常包含一个或多个操作,用于实现命令的具体行为。
```
class Receiver {
action(): void {
console.log("Receiver action.");
}
}
```
接下来,我们通过一个例子来说明命令模式的应用。
假设我们正在构建一个简单的遥控器,它有两个按钮,每个按钮都可以控制不同的电器。为了实现这个遥控器,我们可以使用命令模式来封装所有的操作。
首先,我们定义一个命令接口,其中包含 execute() 方法:
```
interface Command {
execute(): void;
}
```
命令模式的优点:
1. 通过把请求封装成命令对象,可以在不同的时刻调用命令、将命令存储起来、传递命令、撤销命令、组合命令等;
2. 可以将发起者和接收者解耦,它们之间只需要共享命令接口,而不需要知道对方的实现细节;
3. 可以方便地添加新的命令类和请求类,符合开闭原则,可以方便地扩展系统的功能。
命令模式的缺点:
1. 可能导致系统有过多的具体命令类,增加了系统的复杂度;
2. 可能导致系统在使用命令模式时,系统中的对象数量增多。
命令模式的适用场景:
1. 当需要将请求发送给多个对象处理,但是不知道实际的接收者时,可以使用命令模式;
2. 当需要对请求进行排队、记录请求日志、撤销操作等功能时,可以使用命令模式;
3. 当需要将一组操作集合在一起形成宏命令时,可以使用命令模式;
4. 当需要将操作参数化,可以使用命令模式。
下面是一个使用命令模式的示例代码:
```javascript
// 定义命令接口
class Command {
execute() {}
}
// 定义具体的命令类
class LightOnCommand extends Command {
constructor(light) {
super();
this.light = light;
}
execute() {
this.light.on();
}
}
class LightOffCommand extends Command {
constructor(light) {
super();
this.light = light;
}
execute() {
this.light.off();
}
}
// 定义请求者
class Light {
on() {
console.log('Light is on');
}
off() {
console.log('Light is off');
}
}
// 定义调用者
class RemoteControl {
constructor() {
this.commands = [];
}
setCommand(command) {
this.commands.push(command);
}
pressButton() {
this.commands.forEach((command) => command.execute());
}
}
// 使用命令模式
const light = new Light();
const lightOnCommand = new LightOnCommand(light);
const lightOffCommand = new LightOffCommand(light);
const remoteControl = new RemoteControl();
remoteControl.setCommand(lightOnCommand);
remoteControl.setCommand(lightOffCommand);
remoteControl.pressButton();
```
在上述示例中,我们定义了命令接口 `Command`,并且定义了具体的命令类 `LightOnCommand` 和 `LightOffCommand`,它们都继承自命令接口。我们还定义了请求者 `Light` 和调用者 `RemoteControl`,其中调用者会将命令存储起来,并且可以调用 `pressButton` 方法来执行命令。
7. 如何使用命令模式
命令模式的使用分为两个部分,第一部分是定义具体的命令类,第二部分是将命令与请求者和接收者解耦,并且将命令对象交由请求者来调用。
以电视遥控器为例,我们先定义一个具体的命令类——打开电视命令类:
```javascript
class OpenTvCommand {
constructor(tv) {
this.tv = tv;
}
execute() {
this.tv.open();
}
}
```
在这个例子中,我们将电视机作为一个接收者传递给了具体的命令类,然后在命令类中定义了具体的执行方法 `execute()`,该方法将执行接收者的操作。在这个例子中,打开电视的命令将调用 `tv` 的 `open()` 方法。
然后我们需要一个请求者,即遥控器类。遥控器类中需要包含一个 `execute()` 方法,该方法将调用具体的命令对象来执行相应的操作。
```javascript
class TvController {
constructor() {
this.openCommand = null;
}
setCommand(command) {
this.openCommand = command;
}
openTv() {
this.openCommand.execute();
}
}
```
在这个例子中,遥控器类中包含了一个具体的命令对象 `openCommand`,并且定义了 `setCommand()` 方法来设置具体的命令对象,以及 `openTv()` 方法来调用具体的命令对象来执行相应的操作。
最后,我们需要一个具体的接收者——电视机类。
```javascript
class Tv {
open() {
console.log('打开电视');
}
close() {
console.log('关闭电视');
}
}
```
在这个例子中,电视机类中包含了具体的操作方法,例如 `open()` 方法和 `close()` 方法。
现在我们可以通过组合具体的命令类、请求者和接收者来完成命令模式的使用:
```javascript
const tv = new Tv();
const openTvCommand = new OpenTvCommand(tv);
const tvController = new TvController();
tvController.setCommand(openTvCommand);
tvController.openTv(); // 打开电视
```
在这个例子中,我们首先实例化了具体的接收者——电视机类,然后将电视机作为参数传递给了具体的命令类——打开电视命令类,并实例化了请求者——遥控器类。接着,我们将具体的命令对象设置给了遥控器类,并调用了遥控器类的 `openTv()` 方法来执行打开电视的命令。执行结果将打印出 "打开电视"。
8. 总结
命令模式是一种将请求和操作解耦的设计模式,将请求者与接收者之间的耦合度降至最低,使得请求者只需要知道如何发出请求。