AI Chat 设计模式:9. 命令模式

本文是该系列的第九篇,采用问答式的方式展开,问题由我提出,答案由 Chat AI 作出,灰色背景的文字则主要是我的一些思考和补充。

问题列表

  • Q.1 介绍下命令模式
  • A.1
  • Q.2 详细说说命令模式适用于啥场景呢
  • A.2
  • Q.3 举一个命令模式的例子,使用 c++
  • A.3
  • Q.4 这个例子不错,但我想看个有具体场景的例子,最好结合场景说一下为什么要使用命令模式
  • A.4
  • Q.5 这里使用命令模式,是因为要做取消订单的功能吗
  • A.5
  • Q.6 这个例子可以继续完善吗,比如增加提交订单,投诉订单的命令
  • A.6
  • Q.7 我发现,你前面给的两个例子里,命令的具体处理逻辑基本是命令对象自己在处理,具体逻辑是否应该交给接受者类来处理呢
  • A.7
  • Q.8 在严格的命令模式设计中,命令的处理是放在哪个类里。我希望你严谨的回答我,而不是迎合我,命令的处理究竟是放在哪里合适?
  • A.8
  • Q.10 画一下订单管理系统那个例子的类图吧
  • A.10
  • Q.11 开始你列举了命令模式的几种适用场景,刚刚举了其中 undo 的场景。我想看看其他场景的例子,分别来一个吧,不用写具体代码,主要分析下思路
  • A.11
  • Q.12 我希望你在举例的时候,能说说这些场景中的角色和命令模式中角色的对应关系
  • A.12
  • Q.13 不错,最后,再总结一下命令模式的优缺点吧
  • A.13

Q.1 介绍下命令模式

A.1

命令模式是一种行为型设计模式,它将请求封装为一个对象,从而使不同的请求能够在客户端和接收者之间解耦。该模式中涉及三个主要角色:命令对象(Command)、调用者(Invoker)和接收者(Receiver)。

命令对象封装了一个特定的请求和对应的操作,并提供了一个执行该操作的方法。命令对象通常具有一个执行操作的execute()方法,以及可能还有撤销操作的undo()方法。

调用者充当请求的发送者和命令对象的执行者,它持有命令对象并调用其execute()方法来执行请求。调用者无需知道请求的具体细节,只需通过命令对象来执行请求。

接收者是真正执行请求操作的对象。它包含了可以执行请求所需的具体逻辑和功能。当接收者收到命令对象的execute()方法调用时,它会执行相应的操作。

命令模式的关键是将请求的发送者与执行者解耦,使得可以灵活地新增、修改和组合不同的请求。通过将请求封装为对象,还可以实现命令的排队、历史记录和撤销等功能。

总结起来,命令模式通过将请求封装为对象,实现了请求的发送者和执行者之间的解耦,从而使系统更加灵活和可扩展。这种模式常见于需要实现撤销、队列操作、事务等功能的场景。

核心就是将请求封装为一个对象,最初的动机就是考虑如何将请求进行传递又不造成耦合,封装成对象是一个顺理成章的想法。在 c 语言中的函数指针也能将请求(函数)传递,能实现类似命令模式的效果,对比一下,可能会更好理解命令模式。

Q.2 详细说说命令模式适用于啥场景呢

A.2

命令模式适用于以下场景:

  1. 想要将请求的发送者和接收者解耦:当需要将一个请求发送者与多个接收者解耦时,可以使用命令模式。这样可以使得发送者不需要知道具体的接收者,只需通过命令对象来执行请求,从而实现解耦。

  2. 需要支持撤销和重做操作:命令模式可以很方便地实现撤销和重做操作,因为每个命令对象都可以包含撤销操作的逻辑。通过将每次执行的命令对象保存在历史记录中,可以轻松地回退和重复操作。

  3. 需要实现请求的队列或延迟执行:命令模式可以将请求封装为对象并放入队列中,然后按顺序执行。这样可以很容易地实现请求的排队、延迟执行、调度和优先级控制等功能。

  4. 需要对请求的参数化或配置:通过使用命令模式,可以将请求操作的参数封装为命令对象的属性,从而实现对请求的参数化或配置。这样可以灵活地改变命令对象的属性,而不需要改变调用者的代码。

  5. 需要实现日志记录、审计或事务功能:命令模式可以方便地记录每个请求的执行日志,实现审计功能。同时,可以通过命令模式将一系列命令封装为一个事务对象,从而保证这些命令可以作为一个单元进行执行、回滚或提交。

总的来说,命令模式适用于需要请求的发送者和接收者解耦,以及支持撤销、队列操作、事务等功能的场景。它提供了一种结构化的方式来处理请求,并使系统更加灵活、可扩展和可维护。

Q.3 举一个命令模式的例子,使用 c++

A.3

好的,下面是一个在C++中实现命令模式的简单示例:

#include <iostream>
#include <vector>
#include <string>// 命令接口
class Command {
public:virtual void execute() = 0;
};// 具体命令类 - 打印消息
class PrintCommand : public Command {
private:std::string message;
public:PrintCommand(const std::string& msg) : message(msg) {}void execute() override {std::cout << "Print Command: " << message << std::endl;}
};// 具体命令类 - 计算平方
class SquareCommand : public Command {
private:int number;
public:SquareCommand(int num) : number(num) {}void execute() override {std::cout << "Square Command: " << number * number << std::endl;}
};// 调用者类
class Invoker {
private:std::vector<Command*> commands;
public:void addCommand(Command* command) {commands.push_back(command);}void executeAll() {for (auto command : commands) {command->execute();}commands.clear(); // 可选步骤,清空命令列表}
};int main() {// 创建命令对象Command* printCommand = new PrintCommand("Hello, World!");Command* squareCommand = new SquareCommand(5);// 创建调用者对象Invoker invoker;invoker.addCommand(printCommand);invoker.addCommand(squareCommand);// 执行命令invoker.executeAll();// 释放内存delete printCommand;delete squareCommand;return 0;
}

在这个例子中,我们定义了一个命令接口(Command),它包括一个纯虚的execute()方法。然后我们创建了两个具体的命令类(PrintCommandSquareCommand),它们分别实现了execute()方法来执行打印消息和计算平方的操作。

我们还创建了一个调用者类(Invoker),它内部维护了一个命令列表,可以通过addCommand()方法添加命令对象,然后通过executeAll()方法依次执行命令。

在main()函数中,我们创建了两个命令对象(PrintCommandSquareCommand),并将它们添加到调用者对象(Invoker)中。最后通过调用InvokerexecuteAll()方法,依次执行所有命令。

通过命令模式,我们可以将请求的发送者和接收者解耦,使得添加新命令或修改命令逻辑变得更加灵活和可扩展。

注意调用者类的写法,维护了一个命令的队列,它与客户端直接交互,客户端可以向其添加命令。

这个例子里没有接收者,命令的处理逻辑直接放在了具体命令类里。虽然标准模式中命令的具体处理逻辑是在接收者类里,具体命令类只需要做调用,但是如果命令的处理逻辑比较简单,可以直接放在具体的命令类中。这样可以简化代码结构,并且在命令对象中直接处理操作可能更直观。

Q.4 这个例子不错,但我想看个有具体场景的例子,最好结合场景说一下为什么要使用命令模式

A.4

好的,以下是一个具体场景的例子,结合场景说明为什么要使用命令模式:

假设我们正在开发一个电子商务网站,其中有一个订单管理系统。订单管理系统中有一个“取消订单”功能,当用户点击取消订单按钮时,系统需要执行一系列操作:检查订单状态、更新数据库、发送通知等。

在这种情况下,可以使用命令模式来实现取消订单功能。以下是相应的代码:

#include <iostream>
#include <string>
#include <vector>// 订单类
class Order {
private:std::string orderId;std::string status;
public:Order(const std::string& id, const std::string& stat) : orderId(id), status(stat) {}std::string getOrderId() const {return orderId;}std::string getStatus() const {return status;}void setStatus(const std::string& newStatus) {status = newStatus;}// 其他订单操作方法...
};// 命令接口
class Command {
public:virtual void execute() = 0;
};// 具体命令类 - 取消订单
class CancelOrderCommand : public Command {
private:Order* order;
public:CancelOrderCommand(Order* ord) : order(ord) {}void execute() override {// 检查订单状态,如果允许取消则执行取消操作if (order->getStatus() == "待发货" || order->getStatus() == "已发货") {std::cout << "订单 " << order->getOrderId() << " 已取消" << std::endl;order->setStatus("已取消");// 更新数据库、发送通知等操作...} else {std::cout << "订单 " << order->getOrderId() << " 无法取消" << std::endl;}}
};// 调用者类
class Invoker {
private:std::vector<Command*> commands;
public:void addCommand(Command* command) {commands.push_back(command);}void executeAll() {for (auto command : commands) {command->execute();}commands.clear();}
};int main() {// 创建订单对象Order* order1 = new Order("1001", "待付款");Order* order2 = new Order("1002", "已发货");// 创建命令对象Command* cancelCommand1 = new CancelOrderCommand(order1);Command* cancelCommand2 = new CancelOrderCommand(order2);// 创建调用者对象Invoker invoker;invoker.addCommand(cancelCommand1);invoker.addCommand(cancelCommand2);// 用户点击取消订单按钮,执行命令invoker.executeAll();// 释放内存delete order1;delete order2;delete cancelCommand1;delete cancelCommand2;return 0;
}

在这个例子中,我们模拟了电子商务网站的订单管理系统。订单类(Order)表示一个订单,其中包含订单ID和订单状态等属性。取消订单功能被实现为具体命令类(CancelOrderCommand),它保存了一个Order对象,通过命令模式来实现取消订单的操作。

CancelOrderCommandexecute()方法中,首先检查订单状态。如果订单状态是"待发货"或"已发货",则将订单状态设置为"已取消",并执行其他操作(例如更新数据库、发送通知等)。否则,输出提示信息表示无法取消订单。

使用命令模式的好处在于,将取消订单的操作封装到命令对象中,使得请求的发送者(例如用户界面)与接收者(订单对象)解耦。将来如果需要修改取消订单的逻辑或增加其他操作,只需修改命令对象的execute()方法,而不需要修改订单类或调用者类的代码。这样可以提高系统的灵活性和可维护性。

另外,命令模式还提供了撤销和重做操作的支持。在本例中,可以添加一个undo()方法来实现订单取消操作的撤销功能,从而可以回滚已经取消的订单。

总之,使用命令模式可以使得订单管理系统

Q.5 这里使用命令模式,是因为要做取消订单的功能吗

A.5

是的,这个例子中使用命令模式的主要原因是为了实现取消订单的功能。命令模式可以将取消订单的操作封装为一个具体的命令对象,使得请求的发送者与接收者解耦。这样,可以通过创建不同的命令对象来执行不同的订单操作,包括取消订单、修改订单状态、删除订单等。

通过命令模式,我们可以更加灵活和可扩展地处理订单管理系统中的各种订单操作。例如,可以轻松地增加新的命令类来实现其他操作,而无需修改订单类或调用者类的代码

此外,命令模式还提供了撤销和重做操作的支持,使得在取消订单后可以撤销该操作并回滚订单状态。

因此,通过使用命令模式,可以实现取消订单功能,并且为订单管理系统提供了结构化的方式来处理各种订单操作,同时提高系统的灵活性、可扩展性和可维护性。

这个例子的具体命令类里承担了部分命令的处理逻辑,增加了与订单类(接收者)的耦合。好处就是黑体字的部分,“可以轻松地增加新的命令类来实现其他操作,而无需修改订单类或调用者类的代码”,让我们继续看看怎么增加新的命令。

Q.6 这个例子可以继续完善吗,比如增加提交订单,投诉订单的命令

A.6

当然可以!以下是针对订单管理系统的进一步完善,增加了提交订单和投诉订单的命令:

#include <iostream>
#include <string>
#include <vector>// 订单类
class Order {
private:std::string orderId;std::string status;
public:Order(const std::string& id) : orderId(id), status("待付款") {}std::string getOrderId() const {return orderId;}std::string getStatus() const {return status;}void setStatus(const std::string& newStatus) {status = newStatus;}// 其他订单操作方法...
};// 命令接口
class Command {
public:virtual void execute() = 0;
};// 具体命令类 - 取消订单
class CancelOrderCommand : public Command {
private:Order* order;
public:CancelOrderCommand(Order* ord) : order(ord) {}void execute() override {// 检查订单状态,如果允许取消则执行取消操作if (order->getStatus() == "待付款" || order->getStatus() == "待发货") {std::cout << "订单 " << order->getOrderId() << " 已取消" << std::endl;order->setStatus("已取消");// 更新数据库、发送通知等操作...} else {std::cout << "订单 " << order->getOrderId() << " 无法取消" << std::endl;}}
};// 具体命令类 - 提交订单
class SubmitOrderCommand : public Command {
private:Order* order;
public:SubmitOrderCommand(Order* ord) : order(ord) {}void execute() override {if (order->getStatus() == "待付款") {std::cout << "订单 " << order->getOrderId() << " 已提交" << std::endl;order->setStatus("已提交");// 更新数据库、发送通知等操作...} else {std::cout << "订单 " << order->getOrderId() << " 无法提交" << std::endl;}}
};// 具体命令类 - 投诉订单
class ComplaintOrderCommand : public Command {
private:Order* order;
public:ComplaintOrderCommand(Order* ord) : order(ord) {}void execute() override {if (order->getStatus() == "已发货") {std::cout << "订单 " << order->getOrderId() << " 已投诉" << std::endl;order->setStatus("已投诉");// 更新数据库、发送通知等操作...} else {std::cout << "订单 " << order->getOrderId() << " 无法投诉" << std::endl;}}
};// 调用者类
class Invoker {
private:std::vector<Command*> commands;
public:void addCommand(Command* command) {commands.push_back(command);}void executeAll() {for (auto command : commands) {command->execute();}commands.clear();}
};int main() {// 创建订单对象Order* order1 = new Order("1001");Order* order2 = new Order("1002");// 创建命令对象Command* cancelCommand1 = new CancelOrderCommand(order1);Command* submitCommand1 = new SubmitOrderCommand(order1);Command* complaintCommand2 = new ComplaintOrderCommand(order2);// 创建调用者对象Invoker invoker;invoker.addCommand(cancelCommand1);invoker.addCommand(submitCommand1);invoker.addCommand(complaintCommand2);// 用户操作,执行命令invoker.executeAll();// 释放内存delete order1;delete order2;delete cancelCommand1;delete submitCommand1;delete complaintCommand2;return 0;
}

在这个更新后的例子中,我们添加了两个新的具体命令类:SubmitOrderCommand(用于提交订单)和ComplaintOrderCommand(用于投诉订单)。

在每个具体命令类的execute()方法中,我们根据订单的状态执行相应的操作。如果订单状态满足要求,就执行对应的操作(例如取消订单、提交订单、投诉订单),并在控制台输出相应的提示信息。

这里新增了两个命令,确实只仅需增加两个具体命令类,而不用修改接收者类或者调用者类。

我还是想看看把命令的全部处理逻辑都交给接收者类来处理的情形,一起看看吧!

Q.7 我发现,你前面给的两个例子里,命令的具体处理逻辑基本是命令对象自己在处理,具体逻辑是否应该交给接受者类来处理呢

A.7

非常抱歉,我之前给出的示例可能引起了一些混淆。你是正确的,命令对象的 execute() 方法应该将具体的逻辑委托给接收者类来处理,而不是在命令对象自身中处理。
非常抱歉之前给出的示例存在问题,以下是修改后的电商订单管理系统的命令模式示例代码,展示了命令对象应该如何委托给接收者类处理具体逻辑:

#include <iostream>
#include <vector>// 接收者类 - 订单管理系统
class OrderSystem {
public:void placeOrder(const std::string& orderId) {std::cout << "下单成功,订单号:" << orderId << std::endl;}void cancelOrder(const std::string& orderId) {std::cout << "取消订单成功,订单号:" << orderId << std::endl;}
};// 命令接口
class Command {
public:virtual void execute() = 0;
};// 具体命令类 - 下单命令
class PlaceOrderCommand : public Command {
private:OrderSystem* orderSystem;std::string orderId;
public:PlaceOrderCommand(OrderSystem* system, const std::string& id) : orderSystem(system), orderId(id) {}void execute() {// 调用接收者类的下单方法orderSystem->placeOrder(orderId);}
};// 具体命令类 - 取消订单命令
class CancelOrderCommand : public Command {
private:OrderSystem* orderSystem;std::string orderId;
public:CancelOrderCommand(OrderSystem* system, const std::string& id) : orderSystem(system), orderId(id) {}void execute() {// 调用接收者类的取消订单方法orderSystem->cancelOrder(orderId);}
};// 调用者类 - 订单管理器
class OrderManager {
private:std::vector<Command*> commands;
public:void addCommand(Command* command) {commands.push_back(command);}void executeCommands() {for (Command* command : commands) {command->execute();}commands.clear();}
};int main() {OrderSystem* orderSystem = new OrderSystem(); // 创建订单管理系统(接收者)对象OrderManager* orderManager = new OrderManager(); // 创建订单管理器(调用者)对象// 创建具体命令对象并添加到订单管理器Command* placeOrderCommand = new PlaceOrderCommand(orderSystem, "1001");Command* cancelOrderCommand = new CancelOrderCommand(orderSystem, "1002");orderManager->addCommand(placeOrderCommand);orderManager->addCommand(cancelOrderCommand);// 执行命令orderManager->executeCommands();delete orderManager;delete placeOrderCommand;delete cancelOrderCommand;delete orderSystem;return 0;
}

在这个示例中,命令对象 PlaceOrderCommandCancelOrderCommandexecute() 方法将具体的操作委托给了接收者类 OrderSystem 中对应的下单和取消订单方法来执行。

再次感谢您的指正和耐心,希望这个修正后的示例能够更准确地说明在电商订单管理系统中,命令对象应该委托给接收者类来处理具体的逻辑。

现在 AI 将命令的处理逻辑全部交给了接收者类来处理,是我们常见的命令模式示例。对比一下最开始的写法,具体命令类里一点业务逻辑也没有,其与接收者类的解耦程度更高。但缺点也是比较明显的,当我们需要新增一个命令时,不仅要增加一个具体命令类,还需要在接收者类里新增一个接口。

这不禁让我想起了鲁迅的话,“复杂度不会消失,只是在转移”。

看到这你是否有困惑应该将命令的具体处理逻辑放在哪呢,我也有一样的困惑。

Q.8 在严格的命令模式设计中,命令的处理是放在哪个类里。我希望你严谨的回答我,而不是迎合我,命令的处理究竟是放在哪里合适?

A.8

非常抱歉之前的回答还没有给出明确的答案。在严格的命令模式设计中,命令的处理应该放在命令接收者类中

命令模式的基本思想是将命令封装成一个对象,使得发送者和接收者解耦。命令对象包含了具体的操作和对应的接收者,在调用命令时,命令对象会委托接收者来执行实际的操作。

命令接收者类是实际执行命令操作的地方,它包含了具体的业务逻辑和资源。接收者类要根据具体的命令进行相应的处理,从而执行所需的操作。

因此,严格的命令模式设计中,命令的处理应该放在命令接收者类中,而不是放在具体的命令类中。具体的命令类只负责向接收者发出执行命令的请求然后由接收者来实际执行相应的操作

请注意,虽然严格的命令模式中命令的处理应放在命令接收者类中,但在实际应用中,根据系统的复杂性和需求的不同,可能会有一些变化或灵活的处理。最终的设计应根据具体情况来确定,以满足系统的需求和设计目标

  1. 在复杂的场景中,还是将命令的具体处理放到命令接收者里吧。比如上面提到的包含资源的场景里,让具体命令类来面对这些资源终归是不合适的。我就一个命令请求,你让我承担这么多?
  2. 最后一段话还是很重要的,要记住:人是活的,设计模式也是活的,并非两个里面必须有一个死

Q.10 画一下订单管理系统那个例子的类图吧

A.10

好的,以下是订单管理系统的命令模式的简化类图示例:
在这里插入图片描述

在这个类图中,存在以下类和角色:

  • Invoker(调用者):负责调用命令对象,并将具体的请求转发给相应的命令对象执行。

调用者是 OrderManager

  • Command(命令):定义了执行的接口,包括 execute() 方法,具体的命令类将实现这个接口。
  • Concrete Command(具体命令):具体的命令类,实现了 Command 接口,负责具体的操作逻辑和调用接收者对象。

具体命令是 PlaceOrderCommand 和 CancelOrderCommand

  • Receiver(接收者):执行实际操作的对象,接收并执行命令对象中封装的操作。

接收者是 OrderSystem

在这个示例中,订单管理系统中的不同操作(例如取消订单、提交订单、投诉订单等)可以被分别封装成具体的命令类,每个命令类都具有一个对应的接收者对象,负责执行相应的具体操作。

通过命令模式,调用者和接收者之间的解耦得以实现,调用者只需要通过调用命令对象的 execute() 方法来触发相应的操作,而不需要直接与接收者进行耦合。

从类图发现,命令模式最大的特点就是调用者和接收者之间得以解耦。

客户端要依赖具体命令类来创建不同的命令,依赖发送者来发送命令,从逻辑上讲,客户端不需要与接收端(OrderSystem)打交道,二者是通过命令来交互的,但在代码里,接收端也是由客户端创建的,所有二者还是有关联。

Q.11 开始你列举了命令模式的几种适用场景,刚刚举了其中 undo 的场景。我想看看其他场景的例子,分别来一个吧,不用写具体代码,主要分析下思路

A.11

当然,我可以给你举几个其他常见的适用场景和相应的例子。以下是一些常见的命令模式适用场景:

  1. 撤销和重做操作:命令模式可以用于实现撤销和重做功能,通过将操作封装为命令对象,可以保存操作的历史记录,并在需要时逆序执行命令。

    示例:一个文本编辑器中,用户可以使用撤销和重做按钮来撤销和恢复之前的文本编辑操作。每次用户执行编辑操作时,会创建相应的命令对象并将其添加到撤销历史记录中。当用户点击撤销按钮时,会从历史记录中取出最近的命令对象并执行其撤销操作。

  2. 异步任务的调度:命令模式可以用于调度和执行异步任务,将任务封装为命令对象,并由调度器对象负责执行。

    示例:一个任务调度系统中,有多个异步任务需要执行,任务之间可能有依赖关系。使用命令模式,每个任务可以被封装为一个命令对象,并在调度器中维护一个任务队列。调度器按照任务的依赖关系和优先级执行任务队列中的命令。

  3. 菜单和按钮的操作:命令模式可以用于处理菜单项和按钮等用户界面元素的操作,将操作封装为命令对象,以实现菜单和按钮的响应功能。

    示例:一个图形编辑器中,用户可以通过菜单栏和工具栏执行诸如绘制形状、修改颜色等操作。每个菜单项和按钮对应一个命令对象,当用户点击菜单项或按钮时,执行相应的命令对象的操作。

  4. 批处理任务:命令模式可以用于批处理任务的执行,将多个操作封装为命令对象,由调用者依次执行这些命令对象。

    示例:一个文件处理系统中,批处理任务要求依次执行一系列操作,例如复制文件、重命名文件、删除文件等。针对每个操作,可以创建一个具体的命令对象,并由批处理任务的调用者按顺序执行这些命令对象。

这些是一些常见的命令模式的应用场景和相应的示例。在实际应用中,根据具体的业务需求和设计要求,可以进一步灵活运用命令模式来解耦和组织系统中的各类操作和请求。

Q.12 我希望你在举例的时候,能说说这些场景中的角色和命令模式中角色的对应关系

A.12

当涉及不同的命令模式适用场景时,以下是对应角色的描述:

  1. 撤销和重做操作:

    • 命令角色:每个编辑操作都封装为一个具体的命令对象,实现了 execute()undo() 方法。
    • 调用者角色:调用者角色,负责执行命令对象的 execute()undo() 方法,并维护历史记录。
    • 接收者角色:执行实际操作的对象,例如文本编辑器中的文本对象。
  2. 异步任务的调度:

    • 命令角色:每个异步任务都封装为一个具体的命令对象,实现了 execute() 方法。
    • 调度器角色:负责管理任务队列,按顺序执行命令对象的 execute() 方法,并处理依赖关系。
    • 接收者角色:执行实际任务的对象。
  3. 菜单和按钮的操作:

    • 命令角色:每个菜单项或按钮操作都封装为一个具体的命令对象,实现了 execute() 方法。
    • 调用者角色:负责执行命令对象的 execute() 方法,响应用户界面元素的操作。
    • 接收者角色:执行实际操作的对象,例如图形编辑器中的绘图类。
  4. 批处理任务:

    • 命令角色:每个操作都封装为一个具体的命令对象,实现了 execute() 方法。
    • 调用者角色:负责按照顺序执行命令对象的 execute() 方法。
    • 接收者角色:执行实际操作的对象。

在这些场景中,命令模式的核心是将操作封装成命令对象,从而实现请求发送者和接收者的解耦。命令对象充当起中介者的角色,它们封装了具体的操作逻辑,并提供一个统一的接口(通常是 execute() 方法),使调用者能够发出请求以执行相应的操作。

调用者则负责调用命令对象的 execute()undo() 方法,并负责维护命令对象的历史记录、任务队列等。接收者角色执行实际的操作,根据命令对象中封装的操作逻辑来完成具体的任务。

通过以上角色的配合,命令模式能够实现各种需求场景中的解耦、扩展、撤销等功能,并提供更灵活和可维护的代码结构。

Q.13 不错,最后,再总结一下命令模式的优缺点吧

A.13

当然!下面是命令模式的优点和缺点的总结:

优点:

  1. 解耦请求发送者和接收者:命令模式通过将请求封装在命令对象中,实现了请求的发送者和接收者之间的解耦。发送者不需要知道具体的接收者,只需要调用命令对象的方法即可。
  2. 容易扩展和维护:通过添加新的命令类,可以方便地扩展和修改系统的功能。同时,由于每个命令对象封装了一个具体的操作,使得系统更易于维护和调试。
  3. 支持撤销和重做:命令模式可以实现撤销和重做功能,通过保存命令对象的历史记录,可以回溯和撤回之前的操作。
  4. 支持任务队列和执行顺序:通过命令模式,可以将命令对象组合成任务队列,并按照一定的顺序执行。

缺点:

  1. 类膨胀:使用命令模式会增加系统中的类和对象数量,特别是在有大量命令类和接收者类时。这可能会增加代码复杂度和维护成本。
  2. 支持请求处理的开销:对于每个具体命令类而言,需要创建一个具体的命令对象,并将其与请求发送者关联。这可能会在一定程度上增加系统的开销。
  3. 可能引入过多的细粒度命令对象:细粒度的命令对象可能会使得系统设计复杂化。过多的细粒度命令对象可能导致命令模式的实际效果不如预期。

综上所述,命令模式在解耦和扩展性方面具有明显的优势,同时还提供了撤销和重做等功能。然而,使用命令模式会引入一些额外的开销,并可能增加系统的复杂性。在使用命令模式时,需要权衡这些优缺点,并根据具体的应用场景来进行选择和设计。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/8510.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于SpringBoot的开源项目/demo汇总

一些基于SpringBoot的开源项目&#xff0c;方便大家学习&#xff0c;参考。持续更新&#xff0c;排名不分先后。也欢迎贡献&#xff08;回复此贴&#xff09;。 1、GitHub - Tencent/APIJSON: &#x1f3c6; 零代码、全功能、强安全 ORM 库 &#x1f680; 后端接口和文档零代码…

Wireshark抓包分析教程(ubuntu版本)

安装 first&#xff0c;多亏我们的C知道&#xff0c;成功安装了wireshark&#xff0c; Steps are as following&#xff1a; 添加wireshark的软件源(PPA:personal package archive(档案)) sudo apt-add-repository ppa:wireshark-dev/stable ppa:wireshark-dev/stable 是一个…

C++语法(26)--- 特殊类设计

C语法&#xff08;25&#xff09;--- 异常与智能指针_哈里沃克的博客-CSDN博客https://blog.csdn.net/m0_63488627/article/details/131537799?spm1001.2014.3001.5501 目录 1.特殊类设计 1.设计一个类&#xff0c;不能被拷贝 C98 C11 2.设计一个类&#xff0c;只能在堆上…

echarts制作多个纵轴的折线图

代码 <script type"text/javascript"> $(function (){var myChart echarts.init(document.getElementById(main));option {color: ["#9bbb59","#0B438B","#4141F1","#F81945","#4bacc6","#F89E19&q…

soft ip与hard ip

ip分soft和hard两种&#xff0c;soft就是纯代码&#xff0c;买过来要自己综合自己pr。hard ip如mem和analog与工艺有关。 mem的lib和lef是memory compiler产生的&#xff0c;基于bitcell&#xff0c;是foundry给的。 我正在「拾陆楼」和朋友们讨论有趣的话题&#xff0c;你⼀起…

CountDownLatch和CyclicBarrier学习

CountDownLatch和CyclicBarrier都有一个计数器 CountDownLatch countDownLatch new CountDownLatch(4); CyclicBarrier cyclicBarrier new CyclicBarrier(4) CountDownLatch 是在 countDownLatch.countDown()执行后 4-1 等到4减到0后&#xff0c;就可以继续执行程序&#x…

【如何训练一个中英翻译模型】LSTM机器翻译seq2seq字符编码(一)

系列文章 【如何训练一个中英翻译模型】LSTM机器翻译seq2seq字符编码&#xff08;一&#xff09; 【如何训练一个中英翻译模型】LSTM机器翻译模型训练与保存&#xff08;二&#xff09; 【如何训练一个中英翻译模型】LSTM机器翻译模型部署&#xff08;三&#xff09; 【如何训…

音频降噪之人声分离

音频降噪 Ultimate Vocal Remover GUI v5.5.1工具使用 1&#xff1a;是什么 集成目前最先进的从音频文件中分离人声的源分离模型。 本工程&#xff0c;将Ultimate Vocal Remover GUI v5.5.1界面工具&#xff0c;改写成可以在服务器端批量推理的工具。 界面仓库地址&#xff…

APISIX 安全评估

背景 有大佬已经对 [apisix攻击面](https://ricterz.me/posts/2021-07-05-apache-apisix-attack- surface-research.txt)做过总结。 本文记录一下自己之前的评估过程。 分析过程 评估哪些模块&#xff1f; 首先我需要知道要评估啥&#xff0c;就像搞渗透时&#xff0c;我得…

Springboot项目打包war配置详解

Springboot项目打包war配置详解 1. 排除内置tomcat依赖2. 添加servlet依赖3. 修改打包方式4. 修改主启动类5. 完整pom.xml6. 效果图 1. 排除内置tomcat依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter…

xmind latex【记录备忘】

xmind latex 换行 换行必须要有\begin{align}和\end{align}&#xff0c;此时再在里面用\才能换行&#xff0c;如果只写112\224是不能换行的

七、Kafka源码分析之网络通信

1、生产者网络设计 架构设计图 2、生产者消息缓存机制 1、RecordAccumulator 将消息缓存到RecordAccumulator收集器中, 最后判断是否要发送。这个加入消息收集器&#xff0c;首先得从 Deque 里找到自己的目标分区&#xff0c;如果没有就新建一个批量消息 Deque 加进入 2、消…

【Windows】不要让你的win键落灰!掌握常用的组合快捷键,使用电脑更高效了

Windows 操作系统提供了丰富的键盘快捷键&#xff0c;能够大幅提高工作效率和操作便利性。在此介绍一些与 Win 键相关的常用快捷键&#xff0c;帮助你更好地利用 Windows 系统。想要在使用电脑时更高效吗&#xff1f;掌握常用的组合快捷键&#xff0c;让你的 Win 键从此不再落灰…

Security+备考我想分想这几点

考试初衷 本人是一名信息安全从业者&#xff0c;听过很多信息安全方面的认证&#xff0c;如CISP、CISSP、CISA&#xff0c;但是没听过Security认证&#xff0c;偶然的机会&#xff0c;我的同事给我介绍了谷安&#xff0c;从这里我才了解到还有Security认证这么一个信息安全认证…

微服务——http客户端Feign

目录 Restemplate方式调用存在的问题 Feign的介绍 基于Feign远程调用 Feign自定义配置 修改日志方式一(基于配置文件) 修改日志方式二(基于java代码) Feign的性能优化 连接池使用方法 Feign_最佳实践分析 方式一: 方式二 实现Feign最佳实践(方式二) 两种解决方案 Re…

通过v-for生成的input无法连续输入

部分代码&#xff1a;通过v-for循环生成el-form-item&#xff0c;生成多个描述输入框 更改之前的代码&#xff08;key绑定的是item&#xff09;&#xff1a; <el-form-item class"forminput" v-for"(item,index) in formdata.description" :key"…

【C语言】文件操作

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在回炉重造C语言&#xff08;2023暑假&#xff09; ✈️专栏&#xff1a;【C语言航路】 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你…

建立TCP连接的各个系统调用

TCP 连接的过程图 服务器 socket() 函数 socket() 返回的 sockfd 是一个描述符。socket()对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字&#xff0c;而socket()用于创建一个socket描述符&#xff08;socket descriptor&#xff09;&#xff0c;它唯一标识…

ks webdid 滑块注册

web和app其实都一样&#xff0c;主要是针对于设备进行风控&#xff0c;web设备叫webdid; webdid注册出来&#xff0c;过了ks滑块激活&#xff0c;测试了主页&#xff0c;评论等接口都可以跑&#xff0c;平均也就2s注册一个&#xff0c;如果开并发那就更快了&#xff1b; 不过一…

hdvp.

hdvp:外部函数文件&#xff0c;函数定义在hdvp中可以传输给任何hdev使用&#xff0c;即可以发给别人使用。同时允许对hdvp进行加密