编程的法则 依赖倒置原则 (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语言) 目录 一、题目描述 二、解题思路 三、代码实现 一、题目描述 二、解题思路 问题要求将三种类型括号匹配,其中包括顺序匹配和数量匹配 使用栈的后进先…...
华为云AI开发平台ModelArts
华为云ModelArts:重塑AI开发流程的“智能引擎”与“创新加速器”! 在人工智能浪潮席卷全球的2025年,企业拥抱AI的意愿空前高涨,但技术门槛高、流程复杂、资源投入巨大的现实,却让许多创新构想止步于实验室。数据科学家…...
第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...
python打卡day49
知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...
无法与IP建立连接,未能下载VSCode服务器
如题,在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈,发现是VSCode版本自动更新惹的祸!!! 在VSCode的帮助->关于这里发现前几天VSCode自动更新了,我的版本号变成了1.100.3 才导致了远程连接出…...
MySQL 8.0 OCP 英文题库解析(十三)
Oracle 为庆祝 MySQL 30 周年,截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始,将英文题库免费公布出来,并进行解析,帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...
Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信
文章目录 Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket(服务端和客户端都要)2. 绑定本地地址和端口&#x…...
Java毕业设计:WML信息查询与后端信息发布系统开发
JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发,实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构,服务器端使用Java Servlet处理请求,数据库采用MySQL存储信息࿰…...
