编程的法则 依赖倒置原则 (Dependency Inversion Principle)包括如何实践
编程的法则 依赖倒置原则 (Dependency Inversion Principle)包括如何实践
flyfish
2017-07-19
2024-07-18
在软件工程中,存在着层次结构,其中上层的业务逻辑依赖于下层的实现细节。如果是直接的依赖关系可能会导致问题,因为较低层级的变化会影响到高层级的稳定性。
这就是依赖倒置原则发挥作用的地方,在软件设计中引入抽象作为中间层,让上层和下层都依赖于这个抽象,而不是彼此直接依赖。
这意味着,无论是在业务逻辑层还是数据访问层,都应该通过接口或抽象类来进行交互,而非具体的实现。这样一来,当需要更改或替换低层的具体实现时,不会对上层产生影响,因为它们只关心抽象的行为,而不在乎具体是如何完成的。
此外,依赖倒置原则还强调,抽象应该是稳定的,不应依赖于具体的实现细节;反之,具体的实现应该依赖于抽象。这样,即使底层实现发生变化,只要抽象不变,整个系统就能保持稳定。
上面的话总结原则的核心思想是:
-
高层模块不应该依赖于低层模块。两者都应该依赖于抽象。
-
抽象不应该依赖于细节。细节应该依赖于抽象。
高层模块不应该依赖于低层模块。两者都应该依赖于抽象。
高层模块 (High Level Modules)通常包含应用程序的业务逻辑或策略。它们决定了应用程序如何运作以及如何实现主要功能。而低层模块 (Low Level Modules)通常是实现具体功能的组件,例如数据库访问、网络通信、文件读写等。
在传统设计中,高层模块直接依赖于低层模块。这种设计的问题在于,如果低层模块发生改变,高层模块也必须相应地修改,导致系统的耦合度过高,不易于维护和扩展。
依赖抽象 意味着在高层模块和低层模块之间引入一个抽象层,通常是接口或抽象类。这样,高层模块和低层模块都依赖于这个抽象层,而不是直接依赖彼此的具体实现。
举例说明
假设有一个应用程序,它需要从不同的数据源读取数据并进行处理。数据源可以是文件、数据库或网络。
-
高层模块 :负责处理数据的逻辑。
-
低层模块 :实现具体的数据读取操作。
#include <iostream>
#include <string>// 抽象层
class DataReader {
public:virtual std::string readData() = 0;virtual ~DataReader() = default;
};class Writer {
public:virtual void write(const std::string& data) = 0;virtual ~Writer() = default;
};// 具体实现
class FileDataReader : public DataReader {
public:std::string readData() override {return "Data from file";}
};class DatabaseDataReader : public DataReader {
public:std::string readData() override {return "Data from database";}
};class NetworkDataReader : public DataReader {
public:std::string readData() override {return "Data from network";}
};class ConsoleWriter : public Writer {
public:void write(const std::string& data) override {std::cout << "Writing to console: " << data << std::endl;}
};class FileWriter : public Writer {
public:void write(const std::string& data) override {// 模拟写入文件std::cout << "Writing to file: " << data << std::endl;}
};// 高层模块
class DataProcessor {DataReader& reader;Writer& writer;
public:DataProcessor(DataReader& r, Writer& w) : reader(r), writer(w) {}void process() {std::string data = reader.readData();writer.write(data);}
};// 调用示例
int main() {FileDataReader fileReader;DatabaseDataReader dbReader;NetworkDataReader netReader;ConsoleWriter consoleWriter;FileWriter fileWriter;DataProcessor fileProcessor(fileReader, consoleWriter);DataProcessor dbProcessor(dbReader, fileWriter);DataProcessor netProcessor(netReader, consoleWriter);fileProcessor.process();dbProcessor.process();netProcessor.process();return 0;
}
解释
-
抽象层 :
DataReader和Writer是两个抽象类,定义了readData和write方法。 -
具体实现 :
FileDataReader、DatabaseDataReader和NetworkDataReader实现了DataReader接口。ConsoleWriter和FileWriter实现了Writer接口。 -
高层模块 :
DataProcessor类依赖于DataReader和Writer接口,实现数据读取和写入的逻辑。 -
调用示例 :在
main函数中,创建了不同的具体实现对象,并传递给DataProcessor类,分别模拟了从文件、数据库和网络读取数据并写入控制台或文件。
输出:
Writing to console: Data from file
Writing to file: Data from database
Writing to console: Data from network
在这种设计中,DataProcessor 类依赖于抽象的 DataReader 接口,而不是具体的 FileDataReader、DatabaseDataReader 或 NetworkDataReader 类。这样,如果我们需要添加新的数据源,只需要实现 DataReader 接口,而不需要修改 DataProcessor 类。
抽象不应该依赖于细节。细节应该依赖于抽象。
这条原则的意思是,抽象层应该保持独立,不受具体实现的影响。具体实现(低层模块)应该依赖于抽象层,而不是反过来。
如果抽象层依赖于具体实现,那么抽象层的改变将导致具体实现的改变,从而违反了依赖倒置原则。希望抽象层稳定,具体实现可以随时更换,而不影响抽象层和高层模块的代码。
举例说明
假设在设计一个日志系统,日志可以输出到控制台、文件或远程服务器。
#include <iostream>
#include <string>// 抽象层
class ILogger {
public:virtual void log(const std::string& message) = 0;virtual ~ILogger() = default;
};// 具体实现
class ConsoleLogger : public ILogger {
public:void log(const std::string& message) override {std::cout << "Console Log: " << message << std::endl;}
};class FileLogger : public ILogger {
public:void log(const std::string& message) override {// 模拟写入文件std::cout << "File Log: " << message << std::endl;}
};class RemoteLogger : public ILogger {
public:void log(const std::string& message) override {// 模拟发送到远程服务器std::cout << "Remote Log: " << message << std::endl;}
};// 高层模块
class Logger {ILogger& logger;
public:Logger(ILogger& l) : logger(l) {}void log(const std::string& message) {logger.log(message);}
};// 调用示例
int main() {ConsoleLogger consoleLogger;FileLogger fileLogger;RemoteLogger remoteLogger;Logger logger1(consoleLogger);Logger logger2(fileLogger);Logger logger3(remoteLogger);logger1.log("This is a console log message.");logger2.log("This is a file log message.");logger3.log("This is a remote log message.");return 0;
}
解释
-
抽象层 :
ILogger是一个抽象类,定义了log方法。 -
具体实现 :
ConsoleLogger、FileLogger和RemoteLogger类实现了ILogger接口,分别将日志输出到控制台、文件和远程服务器。 -
高层模块 :
Logger类依赖于ILogger接口,实现日志记录的逻辑。 -
调用示例 :在
main函数中,创建了不同的具体实现对象,并传递给Logger类,分别模拟了不同的日志记录方式。
输出:
Console Log: This is a console log message.
File Log: This is a file log message.
Remote Log: This is a remote log message.
在这种设计中,ILogger 是一个抽象接口,ConsoleLogger、FileLogger 和 RemoteLogger 类实现了这个接口。Logger 类依赖于抽象的 ILogger 接口,而不是具体的实现。这样,如果需要添加新的日志输出方式,只需要实现 ILogger 接口,而不需要修改 Logger 类。
相关文章:
编程的法则 依赖倒置原则 (Dependency Inversion Principle)包括如何实践
编程的法则 依赖倒置原则 (Dependency Inversion Principle)包括如何实践 flyfish 2017-07-19 2024-07-18 在软件工程中,存在着层次结构,其中上层的业务逻辑依赖于下层的实现细节。如果是直接的依赖关系可能会导致问题…...
[数据集][目标检测]肾结石检测数据集VOC+YOLO格式1299张1类别
数据集格式:Pascal VOC格式YOLO格式(不包含分割路径的txt文件,仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数):1299 标注数量(xml文件个数):1299 标注数量(txt文件个数):1299 标注…...
pxe安装部署
RHEL7为例: ifconfig查看ip 一.环境配置 1.配置软件仓库: mkdir /rhel7 mount /dev/cdrom /rhel7 echo mount /dev/cdrom /rhel74 >> /etc/rc.d/rc,local chmod x /etc/rc.d/rc.local 2.关闭火墙和selinux,下载…...
Linux用户-sudo命令
作者介绍:简历上没有一个精通的运维工程师。希望大家多多关注我,我尽量把自己会的都分享给大家,下面的思维导图也是预计更新的内容和当前进度(不定时更新)。 Linux是一个多用户多任务操作系统,这意味着它可以同时支持多个用户登录并使用系统。…...
Unity强化工程 之 SpriteEditer Multiple
本文仅作笔记学习和分享,不用做任何商业用途 本文包括但不限于unity官方手册,unity唐老狮等教程知识,如有不足还请斧正 1. SpriteEditer Multiple Automatic slicing - Unity 手册 这是用于裁剪图集的模式 应用之后精灵编辑器会看到Slice亮…...
大数据Flink(一百零九):阿里云Flink的基本名称概念
文章目录 阿里云Flink的基本名称概念 一、层次结构 二、概念说明 1、工作空间(Workspace) 2、项目空间(Namespace) 3、资源(Resource) 4、草稿(Draft&#…...
如何利用AI工具延长摸鱼时间、准点下班?
你好同学,我是沐爸,欢迎点赞、收藏和关注!个人知乎、公众号"沐爸空间" 俗话说,不会摸鱼的程序猿不是好的程序猿。同学,你是不是也在为不能准点下班、每天加班、没有时间提升自己而烦恼? 接下来…...
Yarn:一个快速、可靠且安全的JavaScript包管理工具
(创作不易,感谢有你,你的支持,就是我前行的最大动力,如果看完对你有帮助,还请三连支持一波哇ヾ(@^∇^@)ノ) 目录 一、Yarn简介 二、Yarn的安装 1. 使用npm安装Yarn 2. 在macOS上…...
上线前端系统
上线一个静态的前端系统(续) 在eleme服务器上 启动服务 启动rpcbind [rooteleme-static ~]# systemctl restart rpcbind 启动nfs [rooteleme-static ~]# systemctl restart nfs 重启服务 启动smb [rootstatic-server img]# systemctl start smb…...
制作一个不依赖任何基础镜像的docker镜像
1、比如官方提供的hello-world镜像 #docker pull hello-world #docker images hello-world latest feb5d9fea6a5 2 years ago 13.3kB 可以看到这个镜像只有13.3kB 2、# docker run hello-world 只能打印一些信息 3、这个hello-world镜像的dockerfile就下面3行语…...
【拓扑排序topsort】——启动!!!
B3644 【模板】拓扑排序 / 家谱树 #include<bits/stdc.h> #define int long long #define fi first #define se second #define pb push_back #define PII pair<int,int > #define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0) using namespace std; …...
计算机网络-http协议和https的加密原理
HTTP(HyperText Transfer Protocol,超文本传输协议)是用于在万维网(World Wide Web)上传输超文本的基础协议。它定义了客户端(通常是浏览器)和服务器之间的文本数据传输格式和规则。以下是HTTP的…...
共享`pexlinux`数据文件的网络服务
实验环境准备: 1.红帽7主机 2.要全图形安装 3.配置网络为手动,配置网络可用 4.关闭vmware DHCP功能 一、kickstart自动安装脚本制作 1.安装图形化生成kickstart自动脚本安装工具 2.启动图形制作工具 3.图形配置脚本 这里使用的共享方式是http࿰…...
HC32F4A0 10路串口UART 配置
HC32 小华MCU 使用一段时间了,反正芯片BUG 是比较多了,比如串口接收错误后导致再也无法接收,PWM模块无法输出 低电平 , CAN 接收错误导致 输出引脚 CAN_TXD 一直输出脉冲 。。。;好的一面也存在吧,IO 引脚…...
拯救PyCharm:击退IDE崩溃的终极策略
拯救PyCharm:击退IDE崩溃的终极策略 PyCharm,作为开发界的明星IDE之一,以其强大的功能和灵活的定制性深受广大开发者喜爱。然而,即便是这样一款卓越的开发工具,也可能会遇到崩溃的问题,影响开发效率和工作…...
深入解析Unix命令:掌握wc、whereis和which的使用技巧
目录 1. wc命令 2. whereis命令 3. which命令 结论 在Unix和类Unix系统中,wc、whereis和which是三个常用的命令行工具,每个都有着独特的功能和用途。让我们逐个来了解它们的作用和使用方法。 1. wc命令 wc命令是"word count"的缩写&…...
奥运会大规模使用中国AI大模型!
B站:啥都会一点的研究生公众号:啥都会一点的研究生 AI圈最近又发生了啥新鲜事? 巴黎奥运会大规模使用中国 AI 大模型 巴黎奥运会成为一场科技与体育的盛宴,其中包括了大量中国科技的应用。AI 技术将在多个方面发挥作用…...
Linux中的线程3
死锁 在Linux操作系统中,死锁(Deadlock)是指两个或多个进程(或线程)在执行过程中,因互相持有对方所需的资源而又都在等待对方释放资源,导致它们都无法继续执行下去的一种状态。这种僵局会浪费系…...
内网权限维持——利用WMI进行权限维持
文章目录 一、WMI事件订阅机制简介二、利用事件订阅进行权限维持三、防御方式 一、WMI事件订阅机制简介 WMI(Windows Management Instrumentation,Windows管理规范)是windows提供的一种能够直接与系统进行交互的机制,旨在为系统中…...
【数据结构算法经典题目刨析(c语言)】括号匹配问题(图文详解)
💓 博客主页:C-SDN花园GGbond ⏩ 文章专栏:数据结构经典题目刨析(c语言) 目录 一、题目描述 二、解题思路 三、代码实现 一、题目描述 二、解题思路 问题要求将三种类型括号匹配,其中包括顺序匹配和数量匹配 使用栈的后进先…...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...
[Java恶补day16] 238.除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂度…...
【论文阅读28】-CNN-BiLSTM-Attention-(2024)
本文把滑坡位移序列拆开、筛优质因子,再用 CNN-BiLSTM-Attention 来动态预测每个子序列,最后重构出总位移,预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵(S…...
AI病理诊断七剑下天山,医疗未来触手可及
一、病理诊断困局:刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断",医生需通过显微镜观察组织切片,在细胞迷宫中捕捉癌变信号。某省病理质控报告显示,基层医院误诊率达12%-15%,专家会诊…...
基于 TAPD 进行项目管理
起因 自己写了个小工具,仓库用的Github。之前在用markdown进行需求管理,现在随着功能的增加,感觉有点难以管理了,所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD,需要提供一个企业名新建一个项目&#…...
Java数值运算常见陷阱与规避方法
整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...
抽象类和接口(全)
一、抽象类 1.概念:如果⼀个类中没有包含⾜够的信息来描绘⼀个具体的对象,这样的类就是抽象类。 像是没有实际⼯作的⽅法,我们可以把它设计成⼀个抽象⽅法,包含抽象⽅法的类我们称为抽象类。 2.语法 在Java中,⼀个类如果被 abs…...
Sklearn 机器学习 缺失值处理 获取填充失值的统计值
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 使用 Scikit-learn 处理缺失值并提取填充统计信息的完整指南 在机器学习项目中,数据清…...
qt+vs Generated File下的moc_和ui_文件丢失导致 error LNK2001
qt 5.9.7 vs2013 qt add-in 2.3.2 起因是添加一个新的控件类,直接把源文件拖进VS的项目里,然后VS卡住十秒,然后编译就报一堆 error LNK2001 一看项目的Generated Files下的moc_和ui_文件丢失了一部分,导致编译的时候找不到了。因…...
Java多线程实现之Runnable接口深度解析
Java多线程实现之Runnable接口深度解析 一、Runnable接口概述1.1 接口定义1.2 与Thread类的关系1.3 使用Runnable接口的优势 二、Runnable接口的基本实现方式2.1 传统方式实现Runnable接口2.2 使用匿名内部类实现Runnable接口2.3 使用Lambda表达式实现Runnable接口 三、Runnabl…...
