C++笔记之设计模式:setter函数、依赖注入

C++笔记之设计模式:setter函数、依赖注入

请添加图片描述

code review!

文章目录

  • C++笔记之设计模式:setter函数、依赖注入
    • 1.概念
    • 2.基本示例
    • 3.setter函数
    • 4.基本示例+setter函数构成依赖注入
    • 5.概念——ChatGpt
    • 6.构造函数注入示例
    • 7.接口注入示例
    • 8. 构造函数注入的使用场景和用途
    • 9.接口注入的使用场景和用途
    • 10.使用一个简单的业务逻辑来演示构造函数注入和接口注入的使用场景。
      • 10.1.构造函数注入示例
      • 10.2.接口注入示例
    • 11.附加知识:SOLID设计原则
    • 12.附加知识:依赖倒置原则
    • 13.一个图形绘制应用程序。我们将创建一个图形类(Shape),并通过依赖注入来注入不同的绘制器(Drawer)来绘制不同的图形。

1.概念

依赖注入(Dependency Injection,DI)是一种软件设计模式,用于管理和解决组件之间的依赖关系。在传统的编程中,一个对象通常需要在自身内部创建或获取其所依赖的其他对象,这可能会导致紧密耦合的代码,使得代码难以测试、难以维护和难以扩展。依赖注入的目标是通过从外部传递依赖项来解耦组件,提高代码的可测试性、可维护性和灵活性。

依赖注入的主要思想是,一个组件(被称为"受注入对象")不应该负责创建或获取其依赖的对象。相反,这些依赖应该由外部实例化,并通过构造函数、方法参数、属性等方式注入到受注入对象中。这种方式可以在不修改受注入对象的情况下,灵活地替换依赖项的具体实现,以及在测试时传递模拟对象。

依赖注入可以分为以下几种形式:

  1. 构造函数注入:通过构造函数将依赖项传递给受注入对象。这是最常见的依赖注入方式。通过构造函数,依赖关系在对象创建时就被传递,并在整个对象生命周期中保持稳定。

  2. 方法参数注入:通过方法的参数将依赖项传递给对象的方法。这在需要特定依赖项执行特定操作的情况下非常有用。

  3. 属性注入:通过公开的属性将依赖项传递给对象。这种方式可能导致依赖关系的意外更改,因此在使用时需要小心。

  4. 接口注入:通过接口或抽象类定义依赖项,然后将具体实现传递给对象。这种方式允许在运行时替换依赖项的具体实现,实现多态性。

依赖注入的优势包括:

  • 解耦和灵活性:减少了组件之间的紧耦合,使得代码更加灵活、可维护和易于扩展。

  • 可测试性:可以轻松地传递模拟对象或桩对象,以进行单元测试,从而提高代码的测试覆盖率。

  • 可读性:依赖关系在代码中被明确地传递,使代码更易于理解。

简单理解来说就是当依赖的某个对象是通过外部来注入,而不是自己创建。

2.基本示例

在这里插入图片描述

3.setter函数

在这里插入图片描述

4.基本示例+setter函数构成依赖注入

在这里插入图片描述

代码

class Tools {
public:
    virtual void doWork() = 0;
};

class Hammer : public Tools {
public:
    void doWork() override {
        std::cout << "use hammer" << std::endl;
    }
};

class Axe : public Tools {
public:
    void doWork() override {
        std::cout << "use Axe" << std::endl;
    }
};

class Human {
public:
    Human(Tools& t) : tools(t) {}

    void doWork() {
        tools.doWork();
    }

    Tools& tools;
};

void MakeHuman() {
    Hammer hammer;
    Human human1(hammer);
    human1.doWork();

    Axe axe;
    Human human2(axe);
    human2.doWork();
}

int main() {
    MakeHuman();
    return 0;
}

5.概念——ChatGpt

在这里插入图片描述

6.构造函数注入示例

在这里插入图片描述

代码

#include <iostream>

class Dependency {
public:
    void DoSomething() {
        std::cout << "Dependency is doing something." << std::endl;
    }
};

class Service {
public:
    Service(Dependency* dependency) : dependency_(dependency) {}

    void UseDependency() {
        std::cout << "Service is using dependency." << std::endl;
        dependency_->DoSomething();
    }

private:
    Dependency* dependency_;
};

int main() {
    Dependency dependency;
    Service service(&dependency);

    service.UseDependency();

    return 0;
}

7.接口注入示例

在这里插入图片描述

代码

#include <iostream>

class IDependency {
public:
    virtual void DoSomething() = 0;
};

class Dependency : public IDependency {
public:
    void DoSomething() override {
        std::cout << "Dependency is doing something." << std::endl;
    }
};

class Service {
public:
    Service(IDependency* dependency) : dependency_(dependency) {}

    void UseDependency() {
        std::cout << "Service is using dependency." << std::endl;
        dependency_->DoSomething();
    }

private:
    IDependency* dependency_;
};

int main() {
    Dependency dependency;
    Service service(&dependency);

    service.UseDependency();

    return 0;
}

8. 构造函数注入的使用场景和用途

构造函数注入是一种通过类的构造函数将依赖项传递给类的方式。这种方式适用于以下情况:

  • 类需要一个稳定的依赖关系:通过构造函数注入,依赖项在对象创建时被设置,然后在整个对象生命周期内保持不变,确保了稳定的依赖关系。

  • 松耦合和可测试性:依赖项的传递通过构造函数进行,使得类与具体的依赖实现解耦,从而提高了代码的可测试性和可维护性。

  • 代码可读性:通过构造函数明确地传递依赖项,使得类的依赖关系更加明确和清晰。

  • 依赖的外部控制:通过构造函数注入,外部代码可以在创建对象时决定传递的依赖项,从而实现对依赖的更好控制。

9.接口注入的使用场景和用途

接口注入是通过使用接口或抽象类定义依赖关系,然后传递具体实现的方式。这种方式适用于以下情况:

  • 多态性和扩展性:接口注入允许在运行时决定使用的具体实现,从而实现多态性。这对于在不修改现有代码的情况下扩展应用程序非常有用。

  • 模块替换:通过传递不同的实现,可以轻松替换具体的依赖项,从而实现模块的替换和重用。

  • 依赖反转:通过依赖接口而不是具体实现,实现了依赖反转的原则,即高层模块不依赖于低层模块的具体实现细节。

  • 解耦和灵活性:接口注入减少了类之间的紧耦合,从而提高了代码的灵活性和可维护性。

总之,构造函数注入和接口注入都是实现依赖注入的有效方式,可以根据项目需求选择适当的方式。构造函数注入适用于稳定的依赖关系和明确的依赖项传递,而接口注入适用于多态性、扩展性和模块替换的需求。无论使用哪种方式,依赖注入的目标是减少紧耦合,提高代码的可测试性、可维护性和灵活性。

10.使用一个简单的业务逻辑来演示构造函数注入和接口注入的使用场景。

我们将创建一个模拟的报告生成系统,其中包括一个报告生成器和一个数据源。
业务逻辑:
我们有一个报告生成器(ReportGenerator),它依赖于一个数据源(DataSource)。报告生成器通过数据源获取数据,并生成报告。

10.1.构造函数注入示例

在这里插入图片描述

代码

#include <iostream>
#include <string>

// 数据源类,提供获取数据的方法
class DataSource {
public:
    std::string GetData() {
        return "Data from the data source.";
    }
};

// 报告生成器类,依赖于数据源
class ReportGenerator {
public:
    // 构造函数,接收一个数据源对象作为依赖
    ReportGenerator(DataSource* dataSource) : dataSource_(dataSource) {}

    // 生成报告的方法
    void GenerateReport() {
        // 使用数据源获取数据
        std::string data = dataSource_->GetData();
        std::cout << "Generating report with data: " << data << std::endl;
    }

private:
    DataSource* dataSource_;  // 数据源对象的指针
};

int main() {
    DataSource dataSource;  // 创建数据源对象
    ReportGenerator reportGenerator(&dataSource);  // 创建报告生成器,并传入数据源对象

    reportGenerator.GenerateReport();  // 生成报告

    return 0;
}

10.2.接口注入示例

在这里插入图片描述

代码

#include <iostream>
#include <string>

// 数据源接口,定义获取数据的纯虚方法
class IDataSource {
public:
    virtual std::string GetData() = 0;
};

// 数据源类,实现数据源接口,提供获取数据的方法
class DataSource : public IDataSource {
public:
    std::string GetData() override {
        return "Data from the data source.";
    }
};

// 报告生成器类,依赖于数据源接口
class ReportGenerator {
public:
    // 构造函数,接收一个数据源接口的指针作为依赖
    ReportGenerator(IDataSource* dataSource) : dataSource_(dataSource) {}

    // 生成报告的方法
    void GenerateReport() {
        // 使用数据源接口获取数据
        std::string data = dataSource_->GetData();
        std::cout << "Generating report with data: " << data << std::endl;
    }

private:
    IDataSource* dataSource_;  // 数据源接口的指针
};

int main() {
    DataSource dataSource;  // 创建数据源对象
    ReportGenerator reportGenerator(&dataSource);  // 创建报告生成器,并传入数据源对象

    reportGenerator.GenerateReport();  // 生成报告

    return 0;
}

11.附加知识:SOLID设计原则

SOLID是一组五个面向对象编程和设计的原则,旨在帮助开发者创建更加可维护、灵活和可扩展的软件。这些原则是:

  1. 单一职责原则(Single Responsibility Principle,SRP)
    每个类应该有且仅有一个引起变化的原因,即一个类应该只负责一项职责。这有助于将类的职责分离,使代码更加模块化,提高可维护性和可测试性。

  2. 开放封闭原则(Open/Closed Principle,OCP)
    软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。这意味着在不修改现有代码的情况下,可以通过添加新代码来扩展功能。这有助于减少影响已有功能的风险。

  3. 里氏替换原则(Liskov Substitution Principle,LSP)
    子类必须能够替换其基类,而不影响程序的正确性。这意味着子类应该能够保持基类的行为,并且不应该破坏基类的约定。

  4. 接口隔离原则(Interface Segregation Principle,ISP)
    不应该强迫客户端(使用接口的类)依赖于它们不需要的接口。这个原则鼓励将大型接口拆分成更小、更具体的接口,以便客户端只需实现它们所需的部分。

  5. 依赖倒置原则(Dependency Inversion Principle,DIP)
    高层模块不应该依赖于低层模块,两者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。这个原则强调通过依赖于抽象来实现解耦和灵活性。

这些原则共同构成了SOLID原则,它们的目标是指导开发者编写更加灵活、可扩展和易于维护的代码。这些原则并不是僵硬的规则,而是一种指导思想,根据实际情况和项目需求进行适当的应用和权衡。

12.附加知识:依赖倒置原则

依赖倒置原则(Dependency Inversion Principle,DIP)是SOLID设计原则中的一部分,提出了一种关于如何设计和组织软件架构的指导思想。DIP的核心思想是:

  1. 高层模块不应该依赖于低层模块。两者都应该依赖于抽象。
  2. 抽象不应该依赖于细节。细节应该依赖于抽象。

这个原则强调了以下几个关键概念:

  • 高层模块:指的是较高层次的组件、模块或类,它们通常是实现业务逻辑的部分。

  • 低层模块:指的是较低层次的组件、模块或类,它们通常是实现底层细节的部分,比如工具类、数据库访问等。

  • 抽象:指的是接口、抽象类或其他形式的抽象层,用于定义高层和低层之间的通信接口。

  • 细节:指的是具体的实现细节,通常包括具体类、方法等。

依赖倒置原则的目标是减少组件之间的紧耦合,提高系统的灵活性、可维护性和可扩展性。通过将高层模块和低层模块都依赖于抽象,可以实现以下几个好处:

  1. 可替换性:高层模块可以轻松地切换不同的低层实现,而不需要修改高层代码。

  2. 可测试性:通过依赖于抽象,可以更容易地进行单元测试,以及在测试中使用模拟对象或桩对象。

  3. 模块解耦:高层模块和低层模块之间的关系由抽象定义,减少了紧密的依赖关系。

  4. 灵活性:系统更容易适应变化,因为只需要修改抽象或新的低层实现,而不需要修改高层模块。

在之前提供的示例中,使用接口注入的方式就体现了依赖倒置原则。通过依赖于抽象的接口(或者基类),高层模块和低层模块之间的关系由抽象定义,达到了解耦和灵活性的目标。

13.一个图形绘制应用程序。我们将创建一个图形类(Shape),并通过依赖注入来注入不同的绘制器(Drawer)来绘制不同的图形。

在这里插入图片描述

代码

#include <iostream>
#include <string>

// 抽象绘制器接口
class Drawer {
public:
    virtual void Draw(const std::string& shapeType) = 0;
};

// 具体的绘制器实现
class ConsoleDrawer : public Drawer {
public:
    void Draw(const std::string& shapeType) override {
        std::cout << "Drawing " << shapeType << " on console." << std::endl;
    }
};

class FileDrawer : public Drawer {
public:
    void Draw(const std::string& shapeType) override {
        std::cout << "Drawing " << shapeType << " in file." << std::endl;
    }
};

// 图形类,依赖于绘制器接口
class Shape {
public:
    Shape(Drawer* drawer) : drawer_(drawer) {}

    void Draw(const std::string& shapeType) {
        drawer_->Draw(shapeType);
    }

private:
    Drawer* drawer_;
};

int main() {
    ConsoleDrawer consoleDrawer;
    FileDrawer fileDrawer;

    Shape circle(&consoleDrawer);
    Shape rectangle(&fileDrawer);

    circle.Draw("circle");
    rectangle.Draw("rectangle");

    return 0;
}

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

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

相关文章

vue3 实现按钮权限管理

在做后台管理系统时&#xff0c;经常会有权限管理的功能&#xff0c;这里来记录一下关于按钮权限管理的实现方法 1、自定义指令 v-permission。新建js文件用来写指令代码。 export default function btnPerms(app) {app.directive(permission, {mounted(el, binding) {if (!p…

[bug日志]springboot多模块启动,在yml配置启动端口8081,但还是启动了8080

【问题描述】 配置的启动端口是8081&#xff0c;实际启动端口是8080 【解决方法】 1.检查application.yml的配置是否有错误(配置项中&#xff0c;显示白色就错&#xff0c;橙色无措) 2.检查pom.xml的打包方式配置项配置&#xff0c;主pom.xml中的配置项一般为&#xff1a;&l…

【FAQ】云存储EasyCVR视频汇聚平台分发rtsp流时,出现“用户已过期”提示该如何解决?

视频云存储/安防监控EasyCVR视频汇聚平台基于云边端智能协同&#xff0c;支持海量视频的轻量化接入与汇聚、转码与处理、全网智能分发、视频集中存储等。音视频流媒体视频平台EasyCVR拓展性强&#xff0c;视频能力丰富&#xff0c;具体可实现视频监控直播、视频轮播、视频录像、…

SHELL 基础 入门(三) Bash 快捷键 命令执行顺序,详解通配符

目录 Bash 常用快捷键 输入输出重定向 << 用法 输出重定向 命令执行顺序 ; 分号 && || 通配符 传统通配符 &#xff1f; * [ ] [ - ] [ ^ ] 常用字符 强调 &#xff1a; { } 生成序列 Bash 常用快捷键 Ctrl A 把光…

iPhone 14 Pro 动态岛的功能和使用方法详解

当iPhone 14 Pro机型发布时,苹果公司将软件功能与屏幕顶部的药丸状切口创新集成,称之为“灵动岛”,这让许多人感到惊讶。这篇文章解释了它的功能、工作原理,以及你如何与它互动以执行动作。 一、什么是灵动岛?它是如何工作的 在谣言周期的早期‌iPhone 14 Pro‌ 在宣布时…

PDA手持终端联发科安卓主板方案定制_4G5G通讯模块PDA方案开发

PDA手持终端是一种便携式电子设备&#xff0c;具备计算、通讯、存储和数据处理等多种功能&#xff0c;广泛应用于商业、工业、医疗、物流和日常生活等领域。 手持终端pda作为一种快速手持数据处理设备&#xff0c;通常集成了与数据采集和传输密切相关的功能&#xff0c;如条码…

postgresql 条件表达式

postgresql 条件表达式 简单CASE表达式搜索CASE表达式缩写函数nullif函数示例 coalesce函数 总结 简单CASE表达式 语法如下 case 表达式when 值1 then 结果1when 值2 then 结果2else 默认值 end;select e.first_name , e.last_name , case e.department_id when 90 then 管…

【面试专题】Spring篇①

&#x1f4c3;个人主页&#xff1a;个人主页 &#x1f525;系列专栏&#xff1a;Java面试专题 目录 1.你知道 Spring 框架中有哪些重要的模块吗&#xff1f; 2. 谈谈你对 IOC 的认识。 3. 谈谈你对 AOP 的认识。 4.在实际写代码时&#xff0c;有没有用到过 AOP&#xff1f;用…

PDF校对工具正式上线,为用户提供卓越的文档校对解决方案

为满足当下对数字化文档校对的精准需求&#xff0c;我们今日正式发布全新的PDF校对工具。经过深入的技术研发与细致的测试&#xff0c;该工具旨在为企业和个人用户带来一个高效且准确的PDF文档校对平台。 PDF校对工具的主要特性&#xff1a; 1.全面性校对&#xff1a;工具支持…

wazuh--sql检测

官网&#xff1a;Virtual Machine (OVA) - Installation alternatives Wazuh(Wazuh The Open Source Security Platform)&#xff1a;是一整套基于ossec安全检测工具和EFK日志工具构成的终端安全管理工具。不管是将其分类至HIDS&#xff0c;还是EDR&#xff0c;它都是一套通过…

MySQL数据库:内置函数

日期函数 规定&#xff1a;日期&#xff1a;年月日 时间&#xff1a;时分秒 函数名称作用描述current_date()当前日期current_time()当前时间current_timestamp()当前时间戳date(datetime)返回datetime参数的日期部分date_add(date,interval d_value_type)在date中添加…

Kotlin 高阶函数详解

高阶函数 在 Kotlin 中&#xff0c;函数是一等公民&#xff0c;高阶函数是 Kotlin 的一大难点&#xff0c;如果高阶函数不懂的话&#xff0c;那么要学习 Kotlin 中的协程、阅读 Kotlin 的源码是非常难的&#xff0c;因为源码中有太多高阶函数了。 高阶函数的定义 高阶函数的…

vue中form和table标签过长

form标签过长 效果&#xff1a; 代码&#xff1a; <el-form-item v-for"(item,index) in ticketEditTable1" :label"item.fieldNameCn" :propitem.fieldName :key"item.fieldNameCn" overflow"":rules"form[item.fieldName…

Python土力学与基础工程计算.PDF-土的三项组成

5.3 Python求解 Python 求解代码如下&#xff1a; 1. # 定义已知参数 2. G_s 2.7 # 比重 3. w 0.2 # 含水量 4. e 0.6 # 孔隙比 5. gamma_w 9.81 # 水的重度 6. 7. # 根据公式计算饱和度 8. S_r G_s * w / e 9. print("饱和度为", S_r) 10. 11.…

rust库学习-env_logger(actix-web添加彩色日志、rust添加彩色日志 )

文章目录 介绍actix-web启用彩色日志crate地址&json格式日志 我们在进行rust的web开发时&#xff0c;如果不指定日志&#xff0c;就不会有输出&#xff0c;非常不友好 这里我们使用env_logger进行日志打印 介绍 env_logger 需要配合 log 库使用, env_logger 是 Rust 社区…

SpringCloud学习笔记(四)_ZooKeeper注册中心

基于Spring Cloud实现服务的发布与调用。而在18年7月份&#xff0c;Eureka2.0宣布停更了&#xff0c;将不再进行开发&#xff0c;所以对于公司技术选型来说&#xff0c;可能会换用其他方案做注册中心。本章学习便是使用ZooKeeper作为注册中心。 本章使用的zookeeper版本是 3.6…

Android相机-HAL子系统

引言 应用框架要通过拍照预览摄像获得照片或者视频,就需要向相机子系统发出请求, 一个请求对应一组结果 一次可发起多个请求&#xff0c;并且提交请求是非阻塞的&#xff0c;始终按照接收的顺序以队列的形式先进先出地进行顺序处理 一个请求包含了拍摄和拍照配置的所有信息&…

图为科技-边缘计算在智慧医疗领域的作用

边缘计算在智慧医疗领域的作用 随着科技的进步&#xff0c;智慧医疗已成为医疗行业的重要发展趋势。边缘计算作为新兴技术&#xff0c;在智慧医疗领域发挥着越来越重要的作用。本文将介绍边缘计算在智慧医疗领域的应用及其优势&#xff0c;并探讨未来发展方向。 一、边缘计算…

Skywalking Kafka Tracing实现

背景 Skywalking默认场景下&#xff0c;Tracing对于消息队列的发送场景&#xff0c;无法将TraceId传递到下游消费者&#xff0c;但对于微服务场景下&#xff0c;是有大量消息队列的业务场景的&#xff0c;这显然无法满足业务预期。 解决方案 Skywalking的官方社区中&#xf…

远程调试环境配置

目录 一、准备工作 二、ssh连接和xdebug配置 1.ssh连接 2.xdebug配置 三、xdebug调试&#xff0c;访问 一、准备工作 1.安装vscode里面的两个扩展 2.安装对应PHP版本的xdebug 去xdebug官方&#xff0c;复制自己的phpinfo源码到方框里&#xff0c;再点击Analyse Xdebug: …