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

news2024/11/25 15:20:40

本文是该系列的第九篇,采用问答式的方式展开,问题由我提出,答案由 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.coloradmin.cn/o/780902.html

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

相关文章

网络知识点之-BGP协议

边界网关协议&#xff08;BGP&#xff09;是运行于 TCP 上的一种自治系统的路由协议。 BGP 是唯一一个用来处理像因特网大小的网络的协议&#xff0c;也是唯一能够妥善处理好不相关路由域间的多路连接的协议。 BGP 构建在 EGP 的经验之上。 BGP 系统的主要功能是和其他的 BGP 系…

SQL 连接(JOIN)

SQL 连接&#xff08;JOIN&#xff09;是一种用于将两个或多个表中的数据相互匹配的操作&#xff0c;从而形成一个新的数据集合。JOIN 操作常用于查询和分析数据库中的数据&#xff0c;可以根据不同的连接方式返回不同的结果集。 SQL join 用于把来自两个或多个表的行结合起来…

明解STM32—GPIO理论基础知识篇之寄存器原理​

一、前言 在之前的STM32的GPIO理论基础知识中&#xff0c;分别对基本结构和工作模式进行了详细的介绍。GPIO基本结构中主要对GPIO内部的各个功能电路逐一的进行的分析&#xff1b;GPIO工作模式中主要介绍GPIO应用在不同的使用场景下&#xff0c;GPIO端口的静态特征配置和动态的…

FinClip 小程序桌面端商店上线啦

随着技术的不断进步和用户需求的增长&#xff0c;移动应用程序市场日益蓬勃发展。 然而&#xff0c;开发者们面临着一个严峻的挑战&#xff1a;“如何在不同的操作系统上开发和发布应用程序&#xff0c;以满足不同用户群体的需求&#xff1f;”在这方面&#xff0c;使用小程序…

【C++】深层次了解继承,从基础概念到复杂菱形继承问题(文章结尾有菱形继承常见面试题)

1.继承的概念及定义 继承的概念 继承是面向对象设计使代码可以复用的重要手段&#xff0c;它允许程序员在保持原有类特性的基础上进行扩展&#xff0c;增加功能&#xff0c;这样产生的类&#xff0c;称为派生类。 继承的概念并不是固定的&#xff0c;只要能够通过自己的语言…

浅聊webpack的工作原理

参考文献: https://webpack.docschina.org/concepts/ 简述一下 WebPack 是一个模块打包工具&#xff0c;可以使用 WebPack 管理模块。在 webpack 看来&#xff0c;项目里所有资源皆模块&#xff0c;分析模块间的依赖关系&#xff0c;最终编绎输出模块为 HTML、JavaScript、CS…

C++OpenCV(5):图像模糊操作(四种滤波方法)

&#x1f506; 文章首发于我的个人博客&#xff1a;欢迎大佬们来逛逛 &#x1f506; OpenCV项目地址及源代码&#xff1a;点击这里 文章目录 图像模糊操作均值滤波高斯滤波中值滤波双边滤波 图像模糊操作 关于图片的噪声&#xff1a;指的是图片中存在的不必要或者多余的干扰数…

MySQL-多表查询-案例1

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

ARM练习

通过汇编语言完成LED1-3循环点亮练习 .text .global _start _start: /**********LED1点灯**************/ /*初始化RCC*/ RCC_INIT:LDR R0,0X50000A28LDR R1,[R0]ORR R1,R1,#(0X1<<4)ORR R2,R1,#(0x1<<5)STR R1,[R0]STR R2,[R0]LED1_INIT:设置输出模式LDR R0,0X5…

Spring(10) 生成和替换Banner启动图案

目录 1.背景2.推荐网站3.如何集成到spring项目中4.效果展示 1.背景 我们在启动 Spring 项目的时候经常会看到一个 Spring 字样的启动图案。如下所示&#xff1a; 如果我们也想根据我们的内容生成这样的图案&#xff0c;应该怎么操作呢&#xff1f; 2.推荐网站 可以生成这种图…

Docker 制作镜像

自定义制作镜像 我们学习了Dockerfile语法,那么如何应用Dockerfile制作自定义的镜像呢?那今天我们就来实战一下,以主流的微服务Jar 为例子,开启我们自定义制作镜像之旅。 建立简单Springboot项目,并打包成jar 简历demo项目(访问路径 /start/springboot)配置端口,以及利…

P2196 [NOIP1996 提高组] 挖地雷

[NOIP1996 提高组] 挖地雷 题目描述 在一个地图上有 N ( N ≤ 20 ) N\ (N \le 20) N (N≤20) 个地窖&#xff0c;每个地窖中埋有一定数量的地雷。同时&#xff0c;给出地窖之间的连接路径。当地窖及其连接的数据给出之后&#xff0c;某人可以从任一处开始挖地雷&#xff0c;…

飞行动力学 - 第14节-飞机的配平 之 基础点摘要

飞行动力学 - 第14节-飞机的配平 之 基础点摘要 1. 最大上偏配平角2. 重心前限3. 配平曲线4. 空气压缩性影响 & 配平曲线5. 马赫速配平曲线6. 地面效应7. 地效的影响8. 参考资料 1. 最大上偏配平角 升降舵下偏为正从操纵性的角度&#xff0c;重心应该位于【重心前限】 X c…

投个 3D 冰壶,上班玩一玩

本篇文章将介绍如何使用物理引擎和图扑 3D 可视化技术来呈现冰壶运动的模拟。 Oimo.js 物理引擎 Oimo.js 是一个轻量级的物理引擎&#xff0c;它使用 JavaScript 语言编写&#xff0c;并且基于 OimoPhysics 引擎进行了改进和优化。Oimo.js 核心库只有 150K &#xff0c;专门用…

408计算机考研-101-数据结构-基本概念

数据结构 数据结构(Data Structure)是计算机存储、组织数据的方式。 数据结构分为逻辑结构和物理结构(存储结构) 逻辑结构 逻辑结构是指数据之间的相互关系和组织方式。 按照数据元素之间的关系不同&#xff0c;可以分为以下4种&#xff1a; 集合结构线性接口树结构图结构 …

华为OD机试真题 Java 实现【数字游戏】【2023 B卷 100分】,附详细解题思路

TOC 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;每一题都有详细的答题思路、详细的代码注释、样例测试…

操作系统练习:进程间通信(共享内存方式)

说明 本文是《操作系统概念(第九版)》3.4节“进程间通信”的练习。 进程间通信主要由两种模型&#xff1a; 共享内存消息传递 本文使用共享内存的方式实现进程间的通信 创建消息生产者 创建生产者的主要操作包括&#xff1a; 定义共享内存的大小、名称&#xff0c;以及通…

SJA1000的简单调试

文章目录 基本过程SJA1000波特率计算公式验收滤波器使用其他关于CLKOUT测试寄存器初始化过程中会产生中断扩展帧、标准帧的区分计算器 基本过程 SJA1000的接口连接到FPGA上&#xff0c;采用软核进行CAN数据的收发。调试花了1天多的时间&#xff0c;有点波折&#xff0c;下面按…

最新版edge浏览器中安装xpath插件

最近在跟着尚硅谷进行爬虫的学习&#xff0c;老师给出了在谷歌浏览器安装Xpath插件的方法&#xff0c;由于电脑上面未安装谷歌浏览器&#xff0c;所以在网上搜索了在edge上安装Xpath插件的方法&#xff0c;安装完成以后发现使用快捷键 CTRL SHIFTX不能够打开&#xff0c;以为是…

[java安全]CommonsCollections2

文章目录 【java安全】CommonsCollections2前言Commons-Collections4版本中能否调用cc6等链子&#xff1f;PriorityQueue利用链PriorityQueueTransformingComparator**使用了phithon的总结&#xff1a;**POC 进阶POCPOC分析调用链 【java安全】CommonsCollections2 前言 Apac…