当前位置: 首页 > news >正文

深入理解依赖反转原则(DIP)

依赖反转原则是一个比较重要的架构原则,从定义上看是要依赖于抽象,不要依赖于细节, 这个听起来很简单,好像加个接口就完事了,大家的service都是一个接口配一个实现类,是不是依赖倒置呢?很显然不是,不然今天就不用讲了

先举个例子,我们的应用会依赖很多三方的服务,DB、缓存、文件存储、短信邮件服务等等,拿缓存来说,假如使用的是redis缓存,java应用访问redis一般都会使用Jedis,如果我们的业务代码直接使用jedis,那就是依赖细节(如下图左侧),这时候公司要换技术栈,redis不用了,换成memcached,所有直接依赖JedisApi的业务代码全都得变,这就是直接依赖细节的问题:让业务层变得不太稳定

那应该怎么样保证业务层稳定呢?把依赖于细节变成依赖于抽象,业务层要的是redis吗?不!业务层要的示一个缓存服务,那就在业务层定义好缓存服务的接口,至于底层到底是redis还是memcached,业务层不关心(如上图右侧)

那谁来关心实现细节呢?外层关心,外层可以基于redis实现、也可以基于memcached实现,外层负责把业务层定义的知识(CacheService)和具体的三方服务的知识(JedisApi)互相转换、做个适配,所以这一层又叫做适配器层,如果要更换三方服务,也只是外层层变化,对业务层没有影响。

这里有个很关键的点就是,业务层定义的接口里不要有任何与细节相关的东西,不然就没有意义了,比如如果上图右侧的CacheService中还有jedis api里的东西,那就跟左侧没啥区别了,那就不叫依赖于抽象了,又变成依赖于细节了。

从依赖方向上看,上图左侧是业务层依赖于外层,右侧的依赖关系反过来了,变成了外层依赖于业务层,这就是依赖反转的本质。很明显右侧这种依赖方式,使得业务层可以保持稳定,因为规则是业务层定义的,外层只能去适配业务层定义的规则。

把依赖于redis这个例子泛化一点,应用会用到各种各样的三方服务,都是一个道理。

这样你就会发现,系统合理的依赖关系不太应该是这种从上到下的,因为领域层不应该直接依赖下面的基础设施,而应该是这种从外向内的依赖关系(这里我只画了两层同心圆,实际可能会有多层,但不妨碍我们理解)

越靠近中心,它的软件层次就越高,它越是核心业务逻辑,内部区域包含了所有的领域逻辑,而外部区域则包含了系统与外界交互的东西(比如web、rpc接口,以及数据库、文件服务、缓存这些基础设施)。不管是web、rpc还是db、cache,都是系统和外部系统交互的层,都在领域层的外层。

这里有一条非常重要的原则,就是它的依赖关系必须是外层依赖于内层,不能反过来,web层依赖于领域层,与底层设施交互的层也依赖于领域层,内层代码中不应该包含任何外层代码的知识,像类、函数、变量都不行。这样的好处很明显,业务逻辑可以保持纯净、独立,它是整个系统的核心,外层的这些东西对于业务逻辑来说都是插件,可以轻易被替换掉、并且对业务逻辑不产生影响

下面是一张六边形架构图,你会发现它要表达的意思跟前面说的是一个道理。最内层的是领域、业务逻辑,最外层的是客户端、用户、外部系统、下游基础设施

最关键的是它的依赖关系,是从外向内的,外层依赖于内层,外层了解内层的知识,反过来就不行 那怎么样保持这个依赖关系呢?用端口和适配器(所以六边形架构图又叫端口适配器架构,其实端口适配器架构更能表达它的主要思想)

端口其实就是接口,你看它端口都是在内层,意思是接口都由领域层定义,外层这一个个的适配器干嘛的呢,把外部的东西和领域层定义的互相转换,所以叫适配,这和我们前面说的是一个意思 

总结两点: 1:大家在做设计时,首先要明确内层外层的概念,核心业务逻辑就是内层的;2:有了内外层之分后,一个指导原则就是外层可以依赖内层,反过来就不行。这就是依赖反转!

tcp层需要知道http层的东西吗?ip层需要知道tcp层的东西吗?不需要!依赖反转原则应该作为大家在做架构设计时重要规则

相关文章:

  • DevOps持续交付之容器化CICD流水线
  • 用IDEA创建/同步到gitee(码云)远程仓库(保姆级详细)
  • 解除mobaxterm会话14个限制
  • C++每日一练(7):爬山
  • 数字孪生在能源电力行业的技术难点和应用场景
  • Flask 日志
  • 第十一章 Stream消息驱动
  • 5个用于构建Web应用程序的Go Web框架
  • 【记录】开始学习网络安全
  • go slice源码探索(切片、copy、扩容)和go编译源码分析
  • SASS循环
  • Qt+Opencv:人脸检测
  • Unity | 快速修复Animation missing错误
  • Android 跨进程之间通信(IPC)方式之ContentProvider
  • 信号处理设计模式
  • input实现文字超出省略号功能
  • leetcode98. Validate Binary Search Tree
  • Linux编程学习笔记 | Linux多线程学习[2] - 线程的同步
  • miniui datagrid 的客户端分页解决方案 - CS结合
  • nodejs调试方法
  • 多线程事务回滚
  • 关于字符编码你应该知道的事情
  • 基于Android乐音识别(2)
  • 快速体验 Sentinel 集群限流功能,只需简单几步
  • 微服务入门【系列视频课程】
  • 学习笔记DL002:AI、机器学习、表示学习、深度学习,第一次大衰退
  • 用 Swift 编写面向协议的视图
  • 用quicker-worker.js轻松跑一个大数据遍历
  • 原生 js 实现移动端 Touch 滑动反弹
  • # 安徽锐锋科技IDMS系统简介
  • #Js篇:单线程模式同步任务异步任务任务队列事件循环setTimeout() setInterval()
  • #多叉树深度遍历_结合深度学习的视频编码方法--帧内预测
  • #我与Java虚拟机的故事#连载02:“小蓝”陪伴的日日夜夜
  • ( 用例图)定义了系统的功能需求,它是从系统的外部看系统功能,并不描述系统内部对功能的具体实现
  • (16)UiBot:智能化软件机器人(以头歌抓取课程数据为例)
  • (C)一些题4
  • (js)循环条件满足时终止循环
  • (第二周)效能测试
  • (转)大道至简,职场上做人做事做管理
  • .NET / MSBuild 扩展编译时什么时候用 BeforeTargets / AfterTargets 什么时候用 DependsOnTargets?
  • .Net 路由处理厉害了
  • .NET/MSBuild 中的发布路径在哪里呢?如何在扩展编译的时候修改发布路径中的文件呢?
  • .net6Api后台+uniapp导出Excel
  • .NET建议使用的大小写命名原则
  • .vimrc php,修改home目录下的.vimrc文件,vim配置php高亮显示
  • /bin、/sbin、/usr/bin、/usr/sbin
  • @ConditionalOnProperty注解使用说明
  • [Android]通过PhoneLookup读取所有电话号码
  • [C puzzle book] types
  • [c]统计数字
  • [elastic 8.x]java客户端连接elasticsearch与操作索引与文档
  • [js] 正则表达式
  • [LeetCode]—Copy List with Random Pointer 深度复制带“任意指针”的链表
  • [LeetCode]Multiply Strings
  • [Linux] PHP程序员玩转Linux系列-telnet轻松使用邮箱