依赖倒转原则

依赖倒转原则 (Dependency Inversion Principle, DIP) 是面向对象设计中 SOLID 原则的第五个原则。

它包含两条核心思想:

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

    • 高层模块 (High-level modules): 通常包含复杂的业务逻辑和策略,是应用程序的核心。

    • 低层模块 (Low-level modules): 通常提供一些基础的、具体的实现功能,如数据库操作、文件读写、网络通信等。

  2. 抽象不应该依赖于细节。细节应该依赖于抽象。

    • 抽象 (Abstractions): 通常指接口 (Interface) 或抽象类 (Abstract Class)。

    • 细节 (Details): 通常指具体的实现类 (Concrete Class)。

简单来说,依赖倒转原则的核心思想是:面向接口编程,而不是面向实现编程。

为什么需要依赖倒转?

在传统的软件设计中,高层模块常常直接依赖于低层模块。例如,一个订单处理模块(高层)可能直接依赖于一个 MySQL 数据库操作模块(低层)。

这种直接依赖的坏处:

  • 紧耦合 (Tight Coupling): 高层模块和低层模块紧密地绑定在一起。

  • 可测试性差 (Poor Testability): 测试高层模块时,必须同时依赖真实的低层模块,难以进行单元测试或模拟(Mock)低层模块。

  • 可扩展性差 (Poor Extensibility): 如果想更换低层模块(例如,从 MySQL 切换到 PostgreSQL,或者从文件日志切换到数据库日志),就需要修改高层模块的代码。

  • 可维护性差 (Poor Maintainability): 低层模块的改动很容易影响到高层模块。

依赖倒转如何解决这些问题?

依赖倒转通过引入一个“抽象层”(通常是接口或抽象类)来解耦高层模块和低层模块:

  1. 高层模块定义它所需要的接口(抽象)。

  2. 高层模块依赖于这个接口,而不是具体的实现类。

  3. 低层模块去实现这个接口。

这样,高层模块不再直接依赖于低层模块的具体实现,而是依赖于一个双方都认可的“契约”(接口)。依赖关系被“倒转”了:原本是高层依赖低层,现在是低层(实现细节)依赖于高层(定义的抽象)。

一个简单的例子:

不遵循 DIP 的设计:

// 低层模块:邮件发送器
class EmailSender {public void sendEmail(String message) {System.out.println("Sending email: " + message);}
}
​
// 高层模块:通知服务
class NotificationService {private EmailSender emailSender; // 直接依赖具体实现
​public NotificationService() {this.emailSender = new EmailSender(); // 高层模块负责创建低层模块实例}
​public void sendNotification(String message) {emailSender.sendEmail(message);}
}
​
public class Main {public static void main(String[] args) {NotificationService notificationService = new NotificationService();notificationService.sendNotification("Hello DIP!");}
}

问题:如果现在要增加短信通知,或者想在测试时使用一个假的 EmailSender,NotificationService 就必须修改。

遵循 DIP 的设计:

  1. 定义抽象(接口):

// 抽象:消息发送器接口
interface IMessageSender {void sendMessage(String message);
}
  1. 低层模块实现抽象:

// 低层模块:邮件发送器实现
class EmailSender implements IMessageSender {@Overridepublic void sendMessage(String message) {System.out.println("Sending email: " + message);}
}
​
// 另一个低层模块:短信发送器实现
class SmsSender implements IMessageSender {@Overridepublic void sendMessage(String message) {System.out.println("Sending SMS: " + message);}
}
  1. 高层模块依赖抽象:

// 高层模块:通知服务
class NotificationService {private IMessageSender messageSender; // 依赖于抽象接口
​// 依赖通过构造函数注入 (Dependency Injection)public NotificationService(IMessageSender sender) {this.messageSender = sender;}
​public void sendNotification(String message) {messageSender.sendMessage(message);}
}
  1. 客户端(组装):

public class Main {public static void main(String[] args) {// 使用邮件发送IMessageSender emailSender = new EmailSender();NotificationService emailNotificationService = new NotificationService(emailSender);emailNotificationService.sendNotification("Hello via Email!");
​// 使用短信发送IMessageSender smsSender = new SmsSender();NotificationService smsNotificationService = new NotificationService(smsSender);smsNotificationService.sendNotification("Hello via SMS!");}
}

遵循 DIP 的好处:

  • 松耦合 (Loose Coupling): 高层模块和低层模块通过抽象解耦。NotificationService 不再关心具体的发送方式是邮件还是短信,只要它实现了 IMessageSender 接口即可。

  • 可测试性增强 (Improved Testability): 在测试 NotificationService 时,可以轻松地传入一个模拟的 IMessageSender 实现 (Mock Object),而不需要真实的邮件或短信发送环境。

  • 可扩展性增强 (Improved Extensibility): 如果需要增加新的通知方式(如微信通知),只需创建一个新的类实现 IMessageSender 接口,然后将其注入到 NotificationService 中,而无需修改 NotificationService 本身。

  • 可维护性增强 (Improved Maintainability): 修改低层模块的具体实现(如 EmailSender 内部的邮件发送逻辑)不会影响到高层模块 NotificationService,只要接口契约不变。

如何实现依赖倒转?

  • 接口 (Interfaces): 最常见的方式。

  • 抽象类 (Abstract Classes): 也可以作为抽象。

  • 依赖注入 (Dependency Injection, DI): 一种常用的实现依赖倒转的技术模式。高层模块不自己创建依赖对象,而是通过外部(如构造函数、setter 方法、或 DI 容器)将依赖的抽象实例“注入”进来。

总结:

依赖倒转原则指导我们设计出更加灵活、可维护和可测试的系统。它强调了抽象的重要性,并鼓励我们将依赖关系建立在稳定的抽象之上,而不是易变的具体实现之上。这使得系统的各个部分可以独立地演化和替换,从而提高了软件的整体质量。

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

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

相关文章

AI赋能引爆短剧全球化风潮,腾讯云媒体处理助力短剧平台出海吸金

2023年,中国短剧市场以全平台8000万日投放、近500亿规模的爆发式增长震惊行业。紧凑的内容、爽快的剧情令国内观众迅速爱上了几分钟一集的微短剧。然而在平台内卷、监管收紧、巨头入场等因素的冲击下,不到两年时间,这条赛道就已陷入红海。但与…

开源第三方库发展现状

摘要:当前,开源第三方库生态正呈现爆发式增长趋势。GitHub 目前已托管超过 4.2 亿个代码仓库,远超早期统计的 1 亿规模,显示出开发者社区的活跃度持续攀升。同时,37 个主流包管理器所维护的开源组件数量可能已达到数千…

服务器开机自启动服务

前言: 将服务器中脚本开启自启动执行 步骤: 1.创建一个 systemd 服务文件: /etc/systemd/system/ 目录下创建一个新的服务文件。例如,命名为 myapp.service: sudo nano /etc/systemd/system/myapp.service2.编写 [Unit] Descri…

采用Bright Data+n8n+AI打造自动化新闻助手:每天5分钟实现内容日更

一、引言 在信息爆炸的时代,作为科技领域的内容创作者,我每天都要花费2-3小时手动收集行业新闻、撰写摘要并发布到各个社群。直到我发现Bright Datan8nAI这套"黄金组合",才真正实现了从"人工搬运"到"智能自动化&qu…

ROS云课三分钟-3D性能测试supertuxkart和游戏推荐等-国际象棋

ROS云课三分钟-破壁篇GCompris-一小部分支持Edu应用列表-2025-CSDN博客 很多时候,有一种思维定势,将人锁住,人口就是囚。 口人囚~口加人等于囚-CSDN博客 如果突破,跳出问题,再看问题。 这门课程,或者这个平…

学习率及相关优化参数详解:驱动模型高效训练

一、引言 在模型微调的核心参数体系中,优化相关参数是决定训练效率与模型性能的关键变量。它们如同精密机械的齿轮,彼此联动、相互影响,共同调控着模型在参数空间中的搜索路径。本文将围绕学习率、训练轮数、批处理大小、梯度累积和学习率调…

golang 柯里化(Currying)

使用场景:参数在语义上属于不同组,Go 语法无法在单次调用中声明多组可变参数,通过柯里化可以实现分步接收参数。 有的参数是在不同时间段产生,使用Currying可以让函数记住(缓存)参数,避免应用代…

电脑革命家测试版:硬件检测,6MB 轻量无广告 清理垃圾 + 禁用系统更新

各位电脑小白和大神们,我跟你们说啊!有个超牛的东西叫电脑革命家测试版,这是吾爱破解论坛的开发者搞出来的免费无广告系统工具集合,主打硬件检测和系统优化,就像是鲁大师这些软件的平替。下面我给你们唠唠它的核心功能…

R 语言科研绘图第 52 期 --- 网络图-分组

在发表科研论文的过程中,科研绘图是必不可少的,一张好看的图形会是文章很大的加分项。 为了便于使用,本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中,获取方式: R 语言科研绘图模板 --- sciRplothttps://mp.…

EfficientLLM: Efficiency in Large Language Models 高效大模型

目录 第1章:引言第2章:观察与见解2.1 总体观察(Overall Observations)2.2 从EfficientLLM基准中得出的新见解 第3章:背景3.1 大语言模型(LLMs)3.2 提升LLMs效率的方法3.2.1 硬件创新3.2.2 软件优…

SFTP工具类实现文件上传下载_

import com.jcraft.jsch.*; import com.jcraft.jsch.ChannelSftp.LsEntry;import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.*;/*** SFTP工具类*/ public class SftpFile {static Sessio…

RuoYi前后端分离框架将前端dist资源集成到Jar包中独立部署

一、背景 .NET体系下通常采用服务端渲染(如Razor Pages)或直接包含前端资源,而Java Spring Boot项目虽支持静态资源打包,但Vue CLI工程需要独立的构建流程。主管要求将编译后的Vue工程直接嵌入JAR包中方便维护,本人不推荐这样,原因有三: 第一、Vue CLI需要npm run buil…

基于 Flink+Paimon+Hologres 搭建淘天集团湖仓一体数据链路

摘要:本文整理自淘天集团高级数据开发工程师朱奥老师在 Flink Forward Asia 2024 流式湖仓论坛的分享。内容主要为以下五部分: 1、项目背景 2、核心策略 3、解决方案 4、项目价值 5、未来计划 01、项目背景 1.1 当前实时数仓架构 当前的淘天实时架构是从…

SIGCHLD信号--补充

进程一章讲过用wait和waitpid函数清理僵尸进程,父进程可以阻塞等待子进程结束,也可以非阻 塞地查询是否有子进程结束等待清理(也就是轮询的方式)。采用第一种方式,父进程阻塞了就不 能处理自己的工作了;采用第二种方式,父进程在处理自己的工作的同时还要记得时不时地轮询一 下,…

即插即用!全新记忆回溯策略:一种元启发式算法的进化更新机制,含完整免费MATLAB代码

1. 简介 元启发式算法的搜索域总是不断变化,这使得难以适应多样化的优化问题。为了克服上述问题,提出了一种称为记忆回溯策略(MBS)的进化更新机制,包括思维阶段、回忆阶段和记忆阶段。总体而言,MBS的采用通…

Spring AI框架快速入门

​​前言:在经历了八个里程碑式的版本之后(M1~M8),Spring AI 1.0 正式版本,终于在 2025 年 5 月 20 日正式发布,这是另一个新高度的里程碑式的版本,标志着 Spring 生态系统正式全面拥抱人工智能…

Python实战:打造高效通讯录管理系统

📋 编程基础第一期《8-30》–通讯录管理系统 📑 项目介绍 在信息化时代,高效管理个人或团队联系人信息变得尤为重要。本文将带您实现一个基于Python的通讯录管理系统,该系统采用字典数据结构和JSON文件存储,实现了联系…

89. Java 数字和字符串 - Math 类深入解析

文章目录 89. Java 数字和字符串 - Math 类深入解析一、引言二、常量与基本方法2.1 Math 类常量2.2 绝对值和舍入绝对值方法舍入方法最小值和最大值 三、指数与对数方法四、三角函数方法五、总结 89. Java 数字和字符串 - Math 类深入解析 一、引言 在 Java 中,除…

STM32之SG90舵机控制(附视频讲解)

目录 前言: 一、硬件准备与接线 1.1 硬件清单 1.2 接线 二、 SG90舵机简介 1.1 外观 1.2 基本参数 1.3 引脚说明 1.4 控制原理 1.5 特点 1.6 常见问题 三、 单片机简介 四、 程序设计 4.1 定时器配置 4.2 角度控制函数 4.3 主函数调用 五、 总结 …

netstat命令Windows与Linux双平台

深入解析netstat命令:Windows与Linux双平台实战指南 netstat(Network Statistics)是网络诊断中最经典的工具之一,能够帮助用户查看网络连接、端口监听状态、路由表等信息。然而,Windows和Linux系统下的netstat在参数和输出格式上存在差异,容易让人混淆。本文将详细对比两…