命令模式,
命令模式(Command Pattern)是一种行为型设计模式,它将请求封装为一个对象,从而让你使用不同的请求、队列或者日志请求,以及支持可撤销操作。命令模式将请求发送者和接收者解耦,提供了一种在不需要了解请求处理的详细过程下,只关心请求的发送与接收的方式。
1. 角色组成
命令模式主要涉及以下角色:
- 命令(Command):声明一个执行请求的接口,通常定义一个执行方法(
execute()
)。 - 具体命令(ConcreteCommand):实现命令接口,负责调用接收者对象的相关方法。
- 接收者(Receiver):知道如何处理与执行一个请求相关的任务。
- 调用者(Invoker):要求该命令执行请求。
- 客户端(Client):创建具体命令对象并将其传递给调用者。
3. 代码示例
3.1 命令接口(Command)
public interface Command {
void execute();
}
3.2 接收者(Receiver)
public class Light {
public void on() {
System.out.println("The light is ON");
}
public void off() {
System.out.println("The light is OFF");
}
}
3.3 具体命令(ConcreteCommand)
public class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.on();
}
}
public class LightOffCommand implements Command {
private Light light;
public LightOffCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.off();
}
}
3.4 调用者(Invoker)
public class RemoteControl {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void pressButton() {
command.execute();
}
}
3.5 客户端(Client)
public class Client {
public static void main(String[] args) {
// 创建接收者
Light light = new Light();
// 创建命令
Command lightOn = new LightOnCommand(light);
Command lightOff = new LightOffCommand(light);
// 创建调用者并设置命令
RemoteControl remote = new RemoteControl();
// 按下开关
remote.setCommand(lightOn);
remote.pressButton();
// 按下关灯按钮
remote.setCommand(lightOff);
remote.pressButton();
}
}
3.6 运行结果
The light is ON
The light is OFF
这套的层有点多啊,具体命令持有接受者对象,然后调用者又持有命令对象,
为什么要有这么多层?
这些层的引入其实是为了实现 解耦。在命令模式中,调用者和接收者之间的交互被命令对象隔离开了,从而减少了它们之间的直接依赖:
-
调用者与接收者解耦:调用者不再直接操作接收者,所有的请求都通过命令对象发出。这使得调用者不需要了解接收者的实现细节,只需要调用命令的
execute()
方法。 -
命令与接收者解耦:命令对象是
Receiver
的代理,它负责将请求传递给接收者。这样,命令对象对接收者的操作只是一个简单的委托,接收者本身并不需要直接与调用者交互。 -
灵活性:通过这种方式,我们可以方便地添加、删除或修改命令而不需要改变调用者和接收者的实现。例如,如果需要增加撤销操作、恢复操作,或者需要让命令支持参数传递、条件控制等,都可以在具体命令类中实现。
那这个命令模式是不是也用到了桥接模式
命令模式和桥接模式在某些方面确实有相似之处,尤其是在解耦和灵活性方面,但它们的设计目标和应用场景是不同的。
命令模式 (Command Pattern)
命令模式的核心目的是将请求的发送者与请求的接收者解耦,并将请求封装成一个对象,从而使得你能够对请求进行参数化、排队、日志记录、撤销操作等。命令模式的主要关注点是 封装请求 和 控制请求的执行。
-
主要参与者:
- 命令接口:声明
execute()
方法。 - 具体命令类:实现命令接口,封装具体的请求。
- 接收者:执行实际的操作。
- 调用者:触发命令的执行。
- 命令接口:声明
-
应用场景:
- 将请求、调用和执行解耦。
- 支持撤销/恢复操作。
- 支持命令的组合、排队和日志记录等。
桥接模式 (Bridge Pattern)
桥接模式的核心目的是将一个类的抽象部分和它的实现部分分离,使得二者可以独立变化。桥接模式强调将抽象和实现 分离,并通过引入桥接接口来解耦它们之间的关系。这样,可以在不修改现有代码的基础上添加新功能。
-
主要参与者:
- 抽象类:定义抽象的接口或行为。
- 实现类:具体的实现类,实现抽象类的接口。
- 抽象类的子类:调用实现类的功能,通常通过组合或聚合的方式使用实现类。
-
应用场景:
- 当你有多个变体(例如图形、设备、颜色等),而你不希望在一个类层次结构中增加大量的子类。
- 需要独立地扩展抽象和实现部分的功能时。
命令模式和桥接模式的异同
相似之处:
- 解耦:两者都通过引入一个中间层(命令对象或者桥接接口)来解耦客户端和具体实现(命令的接收者或实现类)。这种解耦使得系统更具灵活性和可扩展性。
- 灵活性和扩展性:两种模式都增强了系统的灵活性。命令模式允许动态地选择和组合命令,而桥接模式允许抽象和实现部分独立扩展。
区别:
-
目的不同:
- 命令模式的目的是将请求的发送者和接收者解耦,使得可以对请求进行灵活处理,如撤销、日志记录等。
- 桥接模式的目的是将类的抽象部分和具体实现部分分离,以便它们可以独立地变化,适用于有多个变体的系统。
-
结构不同:
- 命令模式中的“命令”对象封装了请求,它并不关心具体的实现,只负责将请求传递给接收者执行。
- 桥接模式中的“抽象类”与“实现类”通过组合关系来协作,具体的实现类负责实现抽象接口中的方法,而抽象类则为客户端提供接口。
-
应用场景:
- 命令模式主要用于封装请求和命令的执行管理,通常应用于需要动态管理请求、支持撤销/恢复操作的场景。
- 桥接模式则适用于分离抽象和实现的场景,尤其是在你有多个可能的实现或变体时,可以使用桥接模式来避免子类膨胀。
是否用到桥接模式?
虽然命令模式和桥接模式有一些解耦的相似之处,但命令模式并不是“用到了”桥接模式。它们是两种不同的设计模式,解决不同的问题:
- 命令模式主要专注于封装请求和解耦发送者与接收者,而桥接模式则专注于解耦抽象和实现,使得二者可以独立变化。
- 在命令模式中,你确实有一种“接口+实现”的结构(例如命令接口和具体命令类),但这和桥接模式中的“抽象部分”与“实现部分”有本质的不同。命令模式中,命令对象和接收者并非简单的“抽象”和“实现”的关系,而是将请求的发起与执行分离。
总结
命令模式和桥接模式虽然有一些相似之处(如解耦),但它们的设计目标和应用场景是不同的。命令模式关注请求的封装和解耦,而桥接模式关注抽象和实现的分离。因此,命令模式并不“用到了”桥接模式,而是各自解决不同的问题。
调用者,持有 命令 ,命令 持有 接收者, 接收者 负责最终的具体实现,类图
+---------------------+ +-------------------------+
| Invoker | | Command |
+---------------------+ +-------------------------+
| - command: Command | | + execute(): void |
| + set_command(c) | +-------------------------+
| + invoke() | |
+---------------------+ v
|
+-------------------------+
| ConcreteCommand |
+-------------------------+
| - receiver: Receiver |
| + execute(): void |
+-------------------------+
|
v
+-------------------------+
| Receiver |
+-------------------------+
| + action(): void |
+-------------------------+
面向对象思想,
灯 有 开关 两个 api,
开灯,关灯, 两个具体命令 , 持有 这个 灯 接收者, 调用 接收者 的 不同 api
调用者, 持有 不同 命令 ,执行不同 命令 ,
关键是这个 接口实现 和这个 具体实现类 的 持有 , 这个东西, 很多设计模式都这么干
视频里的例子,不咋滴,
没有体现 调用者持有命令, 命令 持有接收者, 接收者 负责最底实现。
命令模式 解耦 理解
哦,中间搞这个命令 就是解耦 请求和执行,
请求 到达 调用者, 调用者 持有命令 , 命令 持有 接收者 , 接收者 负责最终执行。
调用者 不直接 持有 接收者, 调用者 持有 命令 , 命令 持有 接收者。
在命令模式(Command Pattern)中,调用者和接收者之间并没有直接的联系,它们之间的通信是通过命令对象来完成的。这种结构的优势在于 解耦 了请求的发起者(调用者)和请求的执行者(接收者),使得系统变得更加灵活和可扩展。
简单概括流程:
- 请求(调用者) 发出一个操作请求,但它不知道具体的执行细节。
- 调用者持有命令:它持有一个命令对象,而不是直接持有接收者。
- 命令对象 将请求传递给它所持有的 接收者。
- 接收者 执行实际的操作。
为什么要这样设计?
这种设计的关键在于 降低耦合度,这样你就可以:
- 独立扩展:增加新的命令无需修改现有的代码,符合开闭原则。
- 灵活执行:可以动态改变命令的执行顺序、方式或条件。
- 支持撤销和重做操作:命令模式为实现撤销操作提供了基础,通过保存历史命令及其反操作。
举个简单的比喻:
想象你在家里有一个遥控器(调用者),它可以控制多个家电设备(接收者),比如开关灯、开电视、调温度等。遥控器并不关心这些设备是如何工作的,它只是按下一个按钮(命令),然后命令交给具体的设备去执行。遥控器仅仅知道“按按钮”这个行为,但并不直接控制每个设备的具体操作。
这个结构使得你可以随时改变遥控器的按钮映射,甚至可以在遥控器里加入新的功能,而不影响设备的实现方式或其他部分的工作。
通过命令模式来解耦:
- 调用者(例如遥控器)仅通过命令(按钮)来触发操作。
- 命令 负责向 接收者 发送请求,接收者执行具体的动作。
- 接收者 完全独立于调用者,它可以是灯、电视、空调等设备。
这种模式让你可以非常容易地增加新的命令、改变命令的执行方式,而不需要修改调用者或接收者的代码。
哦明白了, 遥控器 持有 命令, 然后发送命令, 命令 到达一个 map 映射, 映射到 具体的接收者去 执行。 遥控器和接受者 中间多了一层命令映射, 这样 就解耦了遥控器和接受者,当 接收者发生变化,不影响遥控器
视频的例子,顾客不直接和厨师对接。解耦了顾客和厨师,就是调用者和接收者,
中间多了一层 招待。
顾客 持有 招待,招待 持有 厨师。
这样如果厨师变了啥的或者语言不通,不影响顾客点单。就是解耦
命令模式和代理模式
有点像,都是加了一个中间层,隔离开两者的直接联系,但是目的不同
代理模式解耦,是为了控制访问等,
命令模式解耦,是为了解耦调用者和接收者,
命令模式:
+----------------+ +----------------+ +----------------+
| Invoker | ---> | Command | ---> | Receiver |
| (调用者) | | (命令对象) | | (接收者) |
+----------------+ +----------------+ +----------------+
| |
v v
(执行命令) (具体执行操作)
- Invoker 发出请求,而 Command 对象将请求和相关的动作封装起来,最后由 Receiver 来执行具体的操作。
代理模式:
+----------------+ +----------------+ +----------------+
| Client | ---> | Proxy | ---> | RealSubject |
| (客户端) | | (代理对象) | | (真实对象) |
+----------------+ +----------------+ +----------------+
| |
v v
(访问代理) (代理实际操作)
- Client 通过 Proxy 与 RealSubject 交互,代理可以在调用前后进行控制和扩展,比如访问控制、懒加载、缓存等。
5. 总结:
- 命令模式和代理模式的确有相似之处,都是通过引入“中间层”来解耦客户端和目标对象,但它们的解耦目的不同:
- 命令模式解耦的是请求的发起者与请求的执行者,关注的是如何封装请求,支持操作的撤销、重做和排队。
- 代理模式解耦的是客户端与实际对象,关注的是如何控制对实际对象的访问,支持权限控制、延迟加载、缓存等功能。
视频给的代码,rubbish
过过过