设计模式24-命令模式

news2024/12/25 9:01:32

设计模式24-命令模式

  • 写在前面
    • 行为变化模式
  • 命令模式的动机
  • 定义与结构
    • 定义
    • 结构
  • C++ 代码推导
  • 优缺点
  • 应用场景
  • 总结
  • 补充
    • 函数对象(Functors)
      • 定义
      • 具体例子
        • 示例:使用函数对象进行自定义排序
        • 代码说明
        • 输出结果
        • 具体应用
      • 优缺点
      • 应用场景
    • 命令模式(Command Pattern)
      • 定义
      • 实现
      • 优缺点
      • 应用场景
    • 对比
    • 选择

写在前面

行为变化模式

  • 在组件的构建过程中,组件行为的变化经常导致组件本身剧烈的变化。行为变化模式,将组件的行为和组件本身进行解构。从而支持组建行为的变化。实现两者之间的松耦合。
  • 行为变化模式通常指的是一类设计模式,它们允许对象在运行时根据状态或环境的变化动态地改变行为。这类模式通过将算法、职责或行为的变化封装起来,使得系统更具灵活性和可扩展性。
  • 行为变化模式通过封装行为、状态或算法的变化,使得系统更加灵活和可扩展。这类模式在解决动态变化需求、减少代码复杂性、提高系统的可维护性等方面具有重要作用。然而,在选择具体模式时,应根据系统的实际需求和复杂度进行权衡,以避免过度设计和不必要的类增加。

典型模式
命令模式
访问器模式

命令模式的动机

  • 在软件构建过程中,行为请求者与行为实现者通常呈现一种紧耦合。但在某些场合比如需要对行为进行,记录,撤销,重做等处理。这种无法抵御变化的解耦合是不合适的。
  • 那么在这种情况下,如何将行为请求者与行为实现者进行解耦?将一组行为抽象为对象,可以实现二者之间的松耦合。
  • 在许多应用中,程序需要向某个对象发送请求,但发送者并不知道请求的接收者是谁,也不知道请求的执行方式。为了实现请求的解耦,命令模式应运而生。命令模式的动机是将“请求”封装为对象,使得可以用不同的请求、队列或日志来参数化对象。命令模式允许请求的发送者与执行者解耦,并且提供了对请求排队、撤销/重做等功能的支持。

定义与结构

定义

命令模式(Command Pattern)是一种行为设计模式,它将一个请求封装为一个对象,从而使你可以用不同的请求对客户端进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。

结构

在这里插入图片描述
这张UML类图描述的是软件设计模式中的命令模式(Command Pattern)。命令模式是一种行为设计模式,它允许将一个请求封装为一个对象,从而使可用不同的请求、队列、日志来参数化其他对象。命令模式也支持可撤销的操作。根据这张图来详细解释命令模式的主要组成部分和它们之间的关系。

  1. 客户端(Client)

    • 客户端是命令模式的发起者。它创建具体的命令对象,并设置命令的接收者。然后,它通过调用者(Invoker)来执行这个命令。
  2. 调用者(Invoker)

    • 调用者对象负责执行命令。它不直接了解命令的接收者(Receiver)和命令的具体实现(即具体命令),而只是持有对命令对象的引用。调用者有一个执行命令的接口,如Execute(),它接受命令对象作为参数并调用命令的execute()方法。
  3. 命令(Command)

    • 命令是一个接口或抽象类,它定义了执行命令的接口execute()。所有的具体命令类都实现这个接口,并在execute()方法中实现具体的执行逻辑。
  4. 接收器(Receiver)

    • 接收器是命令的实际执行者。它知道如何执行与请求相关的操作。在命令模式中,接收器通常会有一些方法(如Action()),这些方法会在命令执行时被调用。
  5. 具体命令(ConcreteCommand)

    • 具体命令是命令接口的实现类。它持有对接收者的引用,并在其execute()方法中调用接收者的方法(如Action())。这样,具体命令就封装了接收者和调用的具体操作。
  6. 状态(State)(在图中为隐式,通过receiverstate的连接表示):

    • 状态通常不是命令模式的核心部分,但在这张图中通过receiverstate的连接暗示了接收器可能与状态有关。在命令模式的实际应用中,接收器可能会维护一些状态信息,这些状态信息会在执行命令时被读取或修改。
  7. 执行(Execute)(在图中以方法形式出现):

    • Execute()方法是调用者用于执行命令的方法。它接受一个命令对象作为参数,并调用该命令对象的execute()方法。Execute()方法和execute()方法名称上的差异表示了它们是不同类中的方法,但它们共同构成了命令模式的核心执行逻辑。

图中的箭头和依赖关系

  • Client指向Invoker:表示客户端创建并设置调用者。
  • Invoker指向Command:表示调用者持有对命令对象的引用。
  • Command指向Execute()(方法):这是命令接口中定义的方法。
  • Execute()(在Invoker中)指向Receiver(通过具体命令):表示调用者通过命令对象间接与接收器交互。
  • ReceiverConcreteCommand之间的关系是隐式的,因为具体命令持有对接收器的引用。
  • ConcreteCommand指向Execute()(在ConcreteCommand中):这是具体命令实现execute()方法的地方。
  • receiver->Action();表示在命令执行时,接收器的Action()方法被调用。

命令模式的主要优点是解耦了调用者和接收者,增加了命令的灵活性,支持可撤销操作和宏命令等高级功能。

C++ 代码推导

下面是一个简单的命令模式实现例子,用于遥控器控制灯光的开关。

#include <iostream>
#include <memory>
#include <vector>

// 命令接口
class Command {
public:
    virtual ~Command() = default;
    virtual void execute() = 0;
    virtual void undo() = 0;
};

// 接收者类
class Light {
public:
    void on() {
        std::cout << "Light is On" << std::endl;
    }

    void off() {
        std::cout << "Light is Off" << std::endl;
    }
};

// 具体命令类:开灯命令
class LightOnCommand : public Command {
public:
    LightOnCommand(Light& light) : light_(light) {}

    void execute() override {
        light_.on();
    }

    void undo() override {
        light_.off();
    }

private:
    Light& light_;
};

// 具体命令类:关灯命令
class LightOffCommand : public Command {
public:
    LightOffCommand(Light& light) : light_(light) {}

    void execute() override {
        light_.off();
    }

    void undo() override {
        light_.on();
    }

private:
    Light& light_;
};

// 调用者类:遥控器
class RemoteControl {
public:
    void setCommand(std::shared_ptr<Command> command) {
        command_ = command;
    }

    void pressButton() {
        if (command_) {
            command_->execute();
            history_.push_back(command_);
        }
    }

    void pressUndo() {
        if (!history_.empty()) {
            history_.back()->undo();
            history_.pop_back();
        }
    }

private:
    std::shared_ptr<Command> command_;
    std::vector<std::shared_ptr<Command>> history_;
};

int main() {
    Light light;
    std::shared_ptr<Command> lightOn = std::make_shared<LightOnCommand>(light);
    std::shared_ptr<Command> lightOff = std::make_shared<LightOffCommand>(light);

    RemoteControl remote;
    remote.setCommand(lightOn);
    remote.pressButton();  // 打开灯

    remote.setCommand(lightOff);
    remote.pressButton();  // 关闭灯

    remote.pressUndo();  // 撤销关闭灯,重新打开灯

    return 0;
}

优缺点

优点

  1. 解耦发送者与接收者:命令模式将请求的发送者与实际执行者解耦,使得可以在不修改发送者代码的情况下更改或扩展接收者。
  2. 增加灵活性:可以容易地将新命令加入系统,支持撤销/重做、日志记录、事务等功能。
  3. 组合命令:可以将多个命令组合成一个复合命令,从而实现更复杂的功能。
  4. 支持宏命令:可以方便地实现批处理,多个命令组合成宏命令一起执行。

缺点

  1. 命令类数量可能增加:对于每个不同的操作,都需要设计一个具体命令类,这可能导致类的数量增多。
  2. 过度设计:对于简单的操作,命令模式可能显得过于复杂。

应用场景

  1. 操作的可撤销性:如文本编辑器中的撤销/重做操作。
  2. 事务性操作:如数据库的事务处理,需要对操作进行记录,以便在出现错误时回滚。
  3. 远程调用:如远程控制系统,需要将操作封装为命令,通过网络发送给远程服务器执行。
  4. 宏命令:如在家居自动化中,一个按键可以执行一系列命令(如同时关闭所有灯光、锁门等)。

命令模式在需要灵活性、扩展性,以及解耦请求和执行者的场景中非常有用。它不仅提高了系统的可维护性,还为功能的拓展提供了良好的支持。

总结

  • 命令模式的根本目的在于将行为请求者与行为实现者进行解耦。在面向对象语言中常见的实现手段是将行为抽象为对象。
  • 实现命令接口的具体命令对象,有时候根据需要可能会保存一些额外的状态信息。通过使用组合模式可以将多个命令封装为一个复合命令也就是宏命令。
  • 命令模式与c++中的函数对象有些类似。但两者定义行为接口的规范有所区别。命令模式以面向对象中的接口实现来定义行为接口规范。更严格,但是具有性能损失的缺点。C++函数对象以函数签名(参数+返回值)来定义行为接口规范。更灵活性能更高。

补充

C++的函数对象(Functors)和命令模式(Command Pattern)都是将操作封装为对象的技术,但它们的实现方式、用途和适用场景有所不同。下面是它们的对比、优缺点以及应用场景的详细说明。

函数对象(Functors)

定义

在C++中,函数对象是指重载了operator()的类对象。通过重载operator(),类的实例能够像函数一样被调用。这种机制允许将行为封装在对象中,并使其可以在需要时调用。

具体例子

C++的函数对象(Functors)通过重载operator()来实现,使得类对象能够像函数一样被调用。这种特性在许多场景下非常有用,比如在标准库算法中传递行为、创建灵活的回调函数等。以下是一个更具体的例子,演示如何使用函数对象来创建一个自定义的排序规则。

示例:使用函数对象进行自定义排序

假设我们有一个包含学生成绩的向量,我们希望根据学生的成绩进行排序,但如果成绩相同,则按学生的名字进行字母排序。我们可以通过定义一个函数对象来实现这个自定义的排序规则。

#include <iostream>
#include <vector>
#include <algorithm>
#include <string>

// 定义一个Student结构体,包含姓名和成绩
struct Student {
    std::string name;
    int grade;
};

// 定义一个函数对象,用于自定义排序规则
class CompareStudents {
public:
    // 重载operator(),实现自定义排序规则
    bool operator()(const Student& a, const Student& b) const {
        if (a.grade != b.grade) {
            return a.grade > b.grade; // 成绩从高到低排序
        } else {
            return a.name < b.name;   // 如果成绩相同,按名字字母顺序排序
        }
    }
};

int main() {
    // 创建一个学生列表
    std::vector<Student> students = {
        {"Alice", 90},
        {"Bob", 85},
        {"Charlie", 90},
        {"David", 85},
        {"Eve", 92}
    };

    // 使用std::sort和自定义的函数对象进行排序
    std::sort(students.begin(), students.end(), CompareStudents());

    // 输出排序后的学生列表
    for (const auto& student : students) {
        std::cout << student.name << ": " << student.grade << std::endl;
    }

    return 0;
}
代码说明
  1. Student 结构体:包含两个成员变量,name(学生姓名)和grade(学生成绩)。
  2. CompareStudents 函数对象:这个类重载了operator(),用于定义自定义的排序规则。
    • 当两个学生的成绩不相同时,按成绩从高到低排序。
    • 当两个学生的成绩相同时,按名字的字母顺序进行排序。
  3. 排序操作
    • 使用std::sort函数对students向量进行排序。
    • std::sort的第三个参数是排序规则,这里传递的是CompareStudents类的一个实例。
输出结果

程序运行后,将按自定义规则对学生列表进行排序,并输出如下结果:

Eve: 92
Alice: 90
Charlie: 90
Bob: 85
David: 85
具体应用

在实际应用中,函数对象可以用于实现任何需要灵活行为的场景。例如:

  • 排序规则:如上例,函数对象可以用于自定义排序规则。
  • 回调函数:函数对象可以作为回调函数传递给其他函数或类,用于事件处理、数据处理等。
  • 算法参数化:在标准库算法如std::for_eachstd::transform等中,函数对象可以作为参数,用于定义具体的操作。

函数对象通过类的机制实现了行为的封装和状态的管理,同时保持了类似函数的调用方式,非常适合需要灵活性和状态保持的场景。

优缺点

优点

  1. 简洁:函数对象通常不需要额外的基础设施(如接口、抽象类),实现较为简洁。
  2. 内联性:由于函数对象通常是小型类,编译器能够更容易地进行内联优化,提升性能。
  3. 状态保持:函数对象可以在对象内部保存状态,且可以多次使用状态。

缺点

  1. 缺乏结构化:对于复杂的行为或系统,函数对象的设计可能会变得不够结构化和灵活。
  2. 扩展性较差:函数对象一般用于实现简单操作,难以扩展到更复杂的行为控制。

应用场景

  1. 标准库算法:如std::sortstd::for_each等标准库算法,常常接受函数对象作为参数。
  2. 简单的回调:在不需要完整命令模式的地方,可以使用函数对象来代替回调函数。

命令模式(Command Pattern)

定义

命令模式是一种行为设计模式,它将请求封装为对象,从而使你可以用不同的请求对客户端进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。

实现

以下是命令模式的一个简化实现:

#include <iostream>
#include <memory>

// 命令接口
class Command {
public:
    virtual ~Command() = default;
    virtual void execute() = 0;
};

// 具体命令类
class LightOnCommand : public Command {
public:
    void execute() override {
        std::cout << "Light is On" << std::endl;
    }
};

// 调用者类
class RemoteControl {
public:
    void setCommand(std::shared_ptr<Command> command) {
        command_ = command;
    }

    void pressButton() {
        if (command_) {
            command_->execute();
        }
    }

private:
    std::shared_ptr<Command> command_;
};

int main() {
    RemoteControl remote;
    std::shared_ptr<Command> lightOn = std::make_shared<LightOnCommand>();

    remote.setCommand(lightOn);
    remote.pressButton();  // 输出 "Light is On"
    return 0;
}

优缺点

优点

  1. 解耦性:命令模式将请求的发送者与接收者解耦,使得可以轻松地交换、增加或删除命令。
  2. 可扩展性:可以很容易地添加新的命令,不影响其他命令的实现。
  3. 支持复杂功能:如命令的排队、撤销、重做、日志记录等。

缺点

  1. 复杂性:实现命令模式需要额外的命令类,这会增加系统的复杂性和代码量,尤其是在简单场景下。
  2. 开销较大:创建命令对象和维护这些对象的生命周期需要额外的资源开销。

应用场景

  1. 远程操作和请求:例如远程控制设备、网络请求处理。
  2. 撤销/重做操作:如文本编辑器、图像处理软件中的撤销/重做功能。
  3. 事务管理:如数据库的事务管理,将一系列操作封装为命令对象。

对比

  • 复杂性:函数对象适用于较为简单的操作,易于实现且不需要复杂的设计模式;命令模式适用于复杂的场景,需要明确的结构和解耦要求。
  • 灵活性:命令模式比函数对象更灵活,适用于需要多种操作的场景,而函数对象更适合单一功能的封装。
  • 状态管理:函数对象天然支持内部状态的管理;命令模式需要在设计时明确状态的存储和操作。

选择

  • 当操作简单且不需要太多结构化时,使用函数对象是更简洁的选择。
  • 当操作复杂、需要解耦或者需要管理操作的生命周期(如撤销、重做)时,命令模式是更好的选择。

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

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

相关文章

查看 CUDA 和 cuDNN 版本

在安装 onnxruntime-gpu 选择版本时需要查看本机 CUDA 和 cuDNN 版本。 查看 CUDA 和 cuDNN 版本 import platform import torchprint("python.version:", platform.python_version()) print("torch.version:", torch.__version__) print("CUDA.vers…

汽车管理 API 接口:开启高效车辆运营新时代

API&#xff08;Application Programming Interface&#xff09;是一种接口&#xff0c;用于不同软件之间的通信。在汽车管理领域&#xff0c;API的应用可以帮助提升车辆运营的效率&#xff0c;让车主和车辆管理者更方便地获取车辆相关信息&#xff0c;进行保养和维修等工作。本…

fastadmin api中无法获取用户信息

控制器使用_initialize方法时&#xff0c;要增加 parent::_initialize(); 这行代码&#xff0c;否则会出现获取不到用户信息的问题&#xff1a; public function _initialize() {// 你的逻辑内容// ...// endparent::_initialize(); }

Chapter 01 Vue入门

前言 Vue 是一个框架&#xff0c;也是一个生态&#xff0c;其功能覆盖了大部分前端开发常见的需求。本文详细讲解了 Vue 的基本概念以及 Vue 开发者工具的安装。 一、Vue简介 ①定义 Vue 是一款用于构建用户界面的渐进式框架。它基于标准 HTML、CSS 和 JavaScript 构建&…

基于RDMA技术的Mayastor解决方案

1. 方案背景和挑战 1.1. Mayastor简介 OpenEBS是一个广受欢迎的开源云原生存储解决方案&#xff0c;托管于CNCF&#xff08;云原生计算基金会&#xff09;之下&#xff0c;旨在通过扩展Kubernetes的能力&#xff0c;为有状态应用提供灵活的持久性存储。Mayastor是OpenEBS项目…

maxscale

入门 官网&#xff1a;https://mariadb.com/kb/en/maxscale/ 开发语言&#xff1a;C 是否支持分片&#xff1a;不支持 支持的数据库&#xff1a;MySQL/Mariadb 路由规则&#xff1a;事务包裹的SQL会全部走写库、没有事务包裹SQL读写库通过设置Hint实现。其它功能通过配置文件实…

微服务通信

1、Feign远程调用 Feign是Spring Cloud提供的⼀个声明式的伪Http客户端&#xff0c; 它使得调⽤远程服务就像调⽤本地服务⼀样简单&#xff0c; 只需要创建⼀个接⼝并添加⼀个注解即可。 Nacos很好的兼容了Feign&#xff0c; Feign 默认集为Ribbon&#xff0c; 所以在Nacos下使…

M8020A J-BERT 高性能比特误码率测试仪

M8020A 比特误码率测试仪 J-BERT M8020A 高性能 BERT 产品综述 Keysight J-BERT M8020A 高性能比特误码率测试仪能够快速、准确地表征传输速率高达 16 或 32 Gb/s 的单通道和多通道器件中的接收机。 M8020A 综合了更广泛的功能&#xff0c;可以简化您的测试系统。 自动对信…

AGV导航方法大盘点:3大类,12小类

导语 大家好&#xff0c;我是社长&#xff0c;老K。专注分享智能制造和智能仓储物流等内容。 在自动化物流领域&#xff0c;自动导引车&#xff08;AGV&#xff09;扮演着至关重要的角色。它们不仅能够提高搬运效率&#xff0c;还能在各种环境中准确无误地完成任务。 而这一切的…

KVM虚拟化之命令行界面创建KVM虚拟机

环境&#xff1a;CentOS8 安装所需软件包 yum groupinstall -y "Virtualization*" 上传一个ISO镜像 使用指令创建KVM虚拟机 给KVM虚拟机创建一个磁盘 -f:指定磁盘类型为qcow2 使用指令创建一个虚拟机 virt-install \ --nameCentos-2 \ --vcpu 1 \ --memory 2048 \ -…

【SpringCloud】(一文通)服务注册/服务发现-Eureka

目 录 一. 背景1.1 问题描述1.2 解决思路1.3 什么是注册中心1.4 CAP理论1.5 常见的注册中心 二. Eureka 介绍三. 搭建Eureka Server3.1 创建 Eureka-server 子模块3.2 引入 eureka-server 依赖3.3 项目构建插件3.4 完善启动类3.5 编写配置文件3.6 启动服务 四. 服务注册4.1 引入…

Docker基础概述、Docker安装、Docker镜像加速、Docker镜像指令

1.为什么学docker 开发环境与测试环境不同&#xff0c;导致错误 因此docker提供解决方法———系统平滑移植&#xff0c;容器虚拟化技术 将代码与软件与配置文件 打包成一个镜像 2.docker的历练 创建一个开发环境内成为镜像文件再用docker使用镜像 3.什么是docker Docke…

泛型篇(Java - 泛型机制)(持续更新迭代)

目录 私聊 一、什么是泛型&#xff0c;泛型有什么用&#xff0c;为什么要用 1. 说法一 1.1 什么是泛型 1.2 泛型的使用 1.3 为什么要用泛型 2. 说法二 2.1 什么是泛型&#xff0c;泛型有什么用&#xff0c;为什么要用 2.2 怎么使用泛型&#xff0c;泛型可以作用在什么…

私有方法加事务注解会导致事务失效

这里idea其实已经提醒了使用事务不能用私有方法&#xff0c;这其实是个常见问题&#xff0c;这里主要就加深印象

XSS复现

目录 XSS简单介绍 一、反射型 1、漏洞逻辑&#xff1a; 为什么有些标签可以触发&#xff0c;有些标签不能触发 可以触发的标签 不能触发的标签 为什么某些标签能触发而某些不能 二、DOM型 1、Ma Spaghet! 要求&#xff1a; 分析&#xff1a; 结果&#xff1a; 2、J…

计算xpclr

1.conda安装xpclr 首先安装流程很轻松 conda create -n xpclr -c bioconda xpclr conda activate xpclr xpclr -h 2.按照要求准备文件 XPCLR - 简书 (jianshu.com) 根据教程准备文件&#xff0c;vcf&#xff0c;计算好的map&#xff0c;以及样本文件txt 其实官网也有介绍…

django学习入门系列之第九点《案例 Flask+MySQL新增用户》

文章目录 1 新增用户往期回顾 1 新增用户 from flask import Flask, render_template, request import pymysqlapp Flask(__name__)# 创建了网址 /nima和函数index的对应关系 # 以后用户在浏览器上访问/nima自动运行函数 app.route("/nima", methods[GET, POST]) d…

最小区间00

题目链接 最小区间00 题目描述 注意点 -10^5 < nums[i][j] < 10^5nums[i] 按非递减顺序排列找到一个 最小 区间&#xff0c;使得 k 个列表中的每个列表至少有一个数包含在其中 解答思路 参照题解&#xff0c;根据滑动窗口完成本题首先将所有的元素都提取出来并按升序…

为修复漏洞而准备的更新破坏了Windows-Linux双启动的计算机

上周是微软支持的 Windows 操作系统每月一次的"星期二补丁"活动。然而&#xff0c;一个本意是修复漏洞的补丁却给一些使用 Windows 和各种版本 Linux 的双启动电脑带来了问题。 Ars Technica报道称&#xff0c;该更新旨在修复名为CVE-2022-2601 的漏洞。该漏洞于 20…

Ps:首选项 - 工具

Ps菜单&#xff1a;编辑/首选项 Edit/Preferences 快捷键&#xff1a;Ctrl K Photoshop 首选项中的“工具” Tool选项卡允许用户根据自己的使用习惯和工作需求来定制 Photoshop 工具的行为。这些设置能够帮助用户提高操作的效率和精确度&#xff0c;提供更加流畅和符合个人习惯…