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.mfbz.cn/a/48022.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

pandas处理什么样的数据?

Pandas 是一个开源的第三方 Python 库&#xff0c;从 Numpy 和 Matplotlib 的基础上构建而来&#xff0c;享有数据分析“三剑客之一”的盛名&#xff08;NumPy、Matplotlib、Pandas&#xff09;。Pandas 已经成为 Python 数据分析的必备高级工具&#xff0c;它的目标是成为强大…

前端工作中常用 CSS 知识点整理

1.1文字溢出省略号 文字单行溢出: overflow: hidden; // 溢出隐藏 text-overflow: ellipsis; // 溢出用省略号显示 white-space: nowrap; // 规定段落中的文本不进行换行 多行文字溢出: overflow: hidden; // 溢出隐藏 text-overflow: …

几百本常用计算机开发语言电子书链接

GitHub - XiangLinPro/IT_book: 本项目收藏这些年来看过或者听过的一些不错的常用的上千本书籍&#xff0c;没准你想找的书就在这里呢&#xff0c;包含了互联网行业大多数书籍和面试经验题目等等。有人工智能系列&#xff08;常用深度学习框架TensorFlow、pytorch、keras。NLP、…

【已解决】React Antd Form.List 表单校验无飘红提示的问题

背景 我想对 Form.List 构建的表单进行校验&#xff0c;比如下拉框中的内容应当至少有一个 XX&#xff0c;表单的长度不能少于多少等等对 List 内容进行校验&#xff0c;并给出飘红提示 问题 比如我有这样一段代码来实现对 list 具体内容的校验&#xff0c;但是写完后发现没有…

MySQL-多表查询-案例1

案例 根据需求完成多表查询的SQL语句的编写将资料汇中准备好的数据的SQL脚本导入到数据库中准备数据中各表的关系如下 具体代码 -- 分类表 create table category(id int unsigned primary key auto_increment comment 主键ID,name varchar(20) not null unique comment 分类名…

华为HCIP第二节-------------------------ISIS

IS-IS&#xff08;Intermediate System to Intermediate System&#xff0c;中间系统到中间系统&#xff09;是ISO &#xff08;International Organization for Standardization&#xff0c;国际标准化组织&#xff09;为它的CLNP&#xff08;ConnectionLessNetwork Protocol&…

WGCLOUD - v3.4.7开始访问地址不用加/wgcloud了

WGCLOUD在v3.4.7版本更新中说明&#xff0c;从v3.4.7版本开始&#xff0c;访问server页面&#xff0c;不用在URL加/wgcloud了 只需要IP和端口就可以了&#xff0c;如&#xff1a;http://localhost:9999

c++网络编程

网络编程模型 c/s 模型&#xff1a;客户端服务器模型b/s 模型&#xff1a;浏览器服务器模型1.tcp网络流程 服务器流程&#xff1a; 1.创建套接字2.完善服务器网络信息结构体3.绑定服务器网络信息结构体4.让服务器处于监听状态5.accept阻塞等待客户端连接信号6.收发数据7.关闭套…

使用EasyPoi实现Excel的按模板样式导出

模板文件 1690342020350导出测试.xlsx 导出文件 如下 1&#xff0c;横向遍历 #fe 使用#fe命令可以实现集合数据的横向拓展&#xff0c;比如模板代码是 {{#fe:maths t.score}}导出的excel里面就会显示会自当前列&#xff0c;向右拓展&#xff0c;效果可参见下面的导出文件…

Retrospectives on the Embodied AI Workshop(嵌入式人工智能研讨会回顾) 论文阅读

论文信息 题目&#xff1a;Retrospectives on the Embodied AI Workshop 作者&#xff1a;Matt Deitke, Dhruv Batra, Yonatan Bisk 来源&#xff1a;arXiv 论文地址&#xff1a;https://arxiv.org/pdf/2210.06849 Abstract 我们的分析重点关注 CVPR Embodied AI Workshop 上…

【LeetCode】114.二叉树展开为链表

题目 给你二叉树的根结点 root &#xff0c;请你将它展开为一个单链表&#xff1a; 展开后的单链表应该同样使用 TreeNode &#xff0c;其中 right 子指针指向链表中下一个结点&#xff0c;而左子指针始终为 null 。展开后的单链表应该与二叉树 先序遍历 顺序相同。 示例 1&…

紫光FPGA试用--软件篇

目录 一 软件安装启动 二 如何打开IP核&#xff1f;查看/修改现有IP核参数&#xff1f; 三 如何定义引脚&#xff1f; 四 如何下载code进入FPGA? 1. 下载到FPGA芯片内&#xff1a; 2.下载到外部FLASH中 五 如何进入在线调试模式&#xff0c;调试步骤 操作步骤&#xff…

Stack

文章目录 定义分类静态栈动态栈 算法应用 定义 在静态内存当中分配的叫做栈&#xff0c;在动态内存中分配的叫做堆。 **红色椭圆圈当中的就是在栈中分配的&#xff0c;蓝色下划线的就是在堆里分配的。**栈和堆表示的是分配数据的一种方式。静态局部变量是通过压栈和出栈来分配…

Sentinel针对IP限流

改造限流策略的针对来源选项 import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;Configuration public class Senti…

每日一题——除自身以外数组的乘积

除自身以外数组的乘积 题目链接 这一题乍一看好像十分简单&#xff0c;先用一趟循环遍历所有数据&#xff0c;得到数据所有元素的乘积&#xff0c;再用一趟循环将这个乘积除以每个元素&#xff0c;这样不就得到了除自身以外数组的乘积吗&#xff1f;我们先来看看代码&#xff…

【iOS】isKindOfClass和isMemberOfClass方法

前言 这个归根结底还是在考察我们对isa走向图和类的继承的理解&#xff0c;也就是苹果官方这幅图&#xff1a; 接下来的函数调用流程请参考这张图。 1 isKindOfClass方法 1.1 objc_opt_isKindOfClass C函数 查看源码可发现&#xff0c;无论是谁调用isKindOfClass方法都会…

了解Unity编辑器之组件篇Event(七)

Event&#xff1a;用于在对象之间进行通信和交互的机制。它可以帮助你实现触发和响应特定动作或状态的逻辑一、Event System&#xff1a;用于处理 UI 事件的系统组件 First Selected 属性&#xff1a;定义了在场景加载或 UI 激活时&#xff0c;哪个 UI 元素将成为首选的选中元素…

Kotlin多平台最佳架构指南

在这篇文章中&#xff0c;我们将对 Kotlin 多平台移动端的最佳架构进行深入探讨。在2023年&#xff0c;作为 Android 开发者&#xff0c;我们会倾向于采用 MVVM 架构&#xff0c;因为它简单、灵活且易于测试。而作为 iOS 开发者&#xff0c;我们可能会选择 MVC、Viper 等架构。…

win11安装appium

node安装 node下载网址: Download | Node.js 安装后对node安装包路径进行配置 npm config set prefix “E:\nodejs\node_global” //设置全局包目录 npm config set cache “E:\nodejs\node_cache” //设置缓存目录npm config list //查看npm配置npm install -g appium //安…

Windows SMB 共享文件夹 排错指南

1 排错可能 是否系统名称为全英文格式 如果不是则 重命名 根据如下排错可能依次设置 1&#xff0c;在运行里面输入"secpol.msc"来启动本地安全设置&#xff0c;\ 然后选择本地策略–>安全选项 -->网络安全LAN 管理器身份验证级别&#xff0c;\ “安全设置”…