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

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

参考笔记:
1.C++笔记之静态成员函数可以在类外部访问私有构造函数吗?
2.C++笔记之设计模式:setter函数、依赖注入
3.C++笔记之两个类的实例之间传递参数——通过构造函数传递类对象的方法详细探究
4.C++笔记之智能指针和单例、依赖注入结合使用
5.C++笔记之两个类的实例之间传递参数的各种方法

请添加图片描述

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;
}

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

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

相关文章

MyBatis入门配置及CURD实现

目录 一、MyBatis简介 1. 什么是 MyBatis ? 2. MyBatis的特性 3. 什么是持久层框架&#xff1f; 二、MyBatis环境配置 2.1 创建maven工程 2.2 导入相关pom依赖 2.3 导入jdbc配置文件 2.4 Mybatis相关插件安装 3.5 Mybatis-cfg.xml 核心配置 2.6 引入Log4j2日志文件…

stm32基于HAL库驱动外部SPI flash制作虚拟U盘

stm32基于HAL库驱动外部SPI flash制作虚拟U盘 &#x1f4cc;参考文章&#xff1a;https://xiaozhuanlan.com/topic/6058234791&#x1f39e;实现效果演示&#xff1a; &#x1f516;上图中的读到的FLASH_ID所指的是针对不同容量&#xff0c;所对应的ID。 //W25X/Q不同容量对应…

Docker安装ES+kibana8.9.1

参考&#xff1a;基于Docker安装Elasticsearch【保姆级教程、内含图解】_docker elasticsearch_Acloasia的博客-CSDN博客 创建网络 docker network create es-net 基于Docker安装Elasticsearch 拉取镜像 docker pull elasticsearch:8.9.1 挂载文件 mkdir -p /usr/local/e…

ubuntu下自启动设置,为了开机自启动launch文件

1、书写sh脚本文件 每隔5秒钟启动一个launch文件&#xff0c;也可以直接在一个launch文件中启动多个&#xff0c;这里为了确保启动顺利&#xff0c;添加了一些延时 #! /bin/bash ### BEGIN INIT sleep 5 gnome-terminal -- bash -c "source /opt/ros/melodic/setup.bash…

c语言每日一练(11)

前言&#xff1a;每日一练系列&#xff0c;每一期都包含5道选择题&#xff0c;2道编程题&#xff0c;博主会尽可能详细地进行讲解&#xff0c;令初学者也能听的清晰。每日一练系列会持续更新&#xff0c;暑假时三天之内必有一更&#xff0c;到了开学之后&#xff0c;将看学业情…

Jira vs Trello:项目管理的深层巅峰对决

引言 项目管理在现代企业运作中起着至关重要的作用。从跨国公司的巨大项目&#xff0c;到创业公司的快速反应&#xff0c;再到个人的日常任务管理&#xff0c;一个好的项目管理工具可以有效地跟踪进度&#xff0c;优化资源分配&#xff0c;确保项目在预定时间内完成。今天&…

【C++杂货铺】探索vector的底层实现

文章目录 一、STL1.1 什么是STL?1.2 STL的版本1.3 STL的六大组件 二、vector的介绍及使用2.1 vector的介绍2.2 vector的使用2.2.1 vector的定义2.2.2 vector iterator2.2.3 vector空间增长问题2.2.4 vector增删查改 2.3 vector\<char\> 可以替代 string 嘛&#xff1f; …

Docker容器:本地私有仓库、harbor私有仓库部署与管理

文章目录 一.本地私有仓库1.本地私有仓库概述2.搭建本地私有仓库3.容器重启策略简介 二.harbor私有仓库部署与管理1.什么是harbor2.Harbor的特性3、Harbor的构成4.Harbor私有仓库架构及数据流向5.harbor部署及配置&#xff08;192.168.198.11&#xff09;&#xff08;1&#xf…

RT-Thread内核学习

内核框架 内核是操作系统最基础也是最重要的部分&#xff0c;内核处于硬件层之上&#xff0c;内核部分包括内核库、实时内核实现。 内核库是为了保证内核能够独立运行的一套小型的类似C库的函数实现子集。这部分根据编译器不同自带C库的情况也会不同。 当使用GNU GCC编译器时&…

自己实现 SpringMVC 底层机制 系列之-实现任务阶段 6-完成控制器方法获取参数-@RequestParam

&#x1f600;前言 自己实现 SpringMVC 底层机制 系列之-实现任务阶段 6-完成控制器方法获取参数-RequestParam &#x1f3e0;个人主页&#xff1a;尘觉主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是尘觉&#xff0c;希望我的文章可以帮助到大家&#xff0c…

python爬虫的js逆向入门到进阶教程文章分享汇总~持续更新

目录 一、内容介绍二 、专栏内容-持续更新1、JS逆向入门2、Js逆向进阶3、爬虫基础知识4、工具与安装5、漫星内容分享 三、星球使用四、b站up主视频推荐 一、内容介绍 二 、专栏内容-持续更新 1、JS逆向入门 2023-08-25》11.常见加密>xx音乐RSA加密 https://articles.zsxq.c…

微信小程序开发教学系列(4)- 数据绑定与事件处理

4. 数据绑定与事件处理 在微信小程序中&#xff0c;数据绑定和事件处理是非常重要的部分。数据绑定可以将数据和页面元素进行关联&#xff0c;实现数据的动态渲染&#xff1b;事件处理则是响应用户的操作&#xff0c;实现交互功能。本章节将详细介绍数据绑定和事件处理的基本原…