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

C# 设计模式六大原则之依赖倒置原则

总目录


前言


1 基本介绍

1. 定义

依赖倒置原则 Dependence Inversion Principle,简称:DIP。

依赖倒置原则:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。

2.分析

在理解依赖倒置原则之前,我们先要理解以下几个概念:

  • 依赖:表示类之间的关系总共有6种,依赖是其中的一种,当A使用了一个B的时候,就是依赖关系。依赖在代码中具体体现为在A类中申明了一个B类作为成员变量或在A类某个方法中使用B类作为参数。

  • 高层模块和底层模块:低层模块 就是被使用的那一方,高层模块就是使用者,这是一个相对的概念。比如 A类使用B类,那么B类就是底层模块,A类就是高层模块

  • 抽象和细节:在C#中抽象一般就是只抽象类和接口,细节就是对应的实现类

有了以上的介绍,我们可以说:

依赖倒置原则就是程序逻辑在传递参数或关联关系时,尽量引用高层次的抽象,不使用具体的类,即使用接口或抽象类来引用参数,声明变量以及处理方法返回值等。

依赖原则说简单点,就是面向接口编程(抽象类和接口 这里统称接口)。接口相对来说是稳定的,而实现类是具体的业务实现,业务的变化是不稳定的。

注:关于类之间的关系说明,详见UML类图 详解。

3. 目的

依赖倒置原则可以使我们的架构更加的稳定、灵活,也能更好地应对需求的变化。因为相对于细节的多变性,抽象的东西是稳定的。所以以抽象为基础搭建起来的架构要比以细节为基础搭建起来的架构要稳定的多。

使用依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性。

2 案例分析

1. 案例1

这里我们以父亲给3岁孩子读 儿童故事书 为例

	//儿童故事书类public class Storybook{//获取故事书内容public string GetContent(){string content = "孙悟空大闹天宫";return content;}}//父亲类public class Father{// 爸爸读书 的方法public void Read(){Console.WriteLine("爸爸开始给孩子讲故事啦!");Storybook storybook = new Storybook();Console.WriteLine($"爸爸正在读:{storybook.GetContent()}");}}

客户端调用:

        static void Main(string[] args){Father father = new Father();father.Read();Console.ReadKey();}

很好实现了需求,我们来看看依赖关系,Father类中引用了Storybook类,因此
Father是直接依赖于Storybook类。

这时需求变更了,妈妈说让爸爸给孩子讲讲编程启蒙书,于是吭哧吭哧修改代码如下:

	//定义一个编程启蒙书类public class CodingBook{public string GetContent(){string content = "二进制,ascii...";return content;}}public class Father{//由于需求变了,因此这里的代码需要做修改public void Read(){//Console.WriteLine("爸爸开始给孩子讲故事啦!");//Storybook storybook = new Storybook();//Console.WriteLine($"爸爸正在读:{storybook.GetContent()}");Console.WriteLine("爸爸开始给孩子讲编程启蒙书啦!");CodingBook codingBook = new CodingBook();Console.WriteLine($"爸爸正在读:{codingBook.GetContent()}");}}

按照这种初级的编程方式我们发现,只要需求稍微发生变化,代码就需要做改动,导致程序极其的不稳定。如果后面需求又变了,需要给孩子读英语启蒙,数学启蒙书等;再者说不定爸爸要加班,需要妈妈来给孩子读书那怎么办呢?其实在这个案例中,具体读什么读物是会发生变化,这就是细节,具体谁来给孩子读也会发生变化,这些细节都是会发生变化的,不稳定的。但是给孩子读书这个抽象的行为是稳定的。如果这时候还是使用传统的OOP思想来解决问题,那么会导致程序不断的在修改,因此我们需要用到依赖倒置原则。

现在我们再来看看使用依赖倒置原则后的代码吧。

首先定义出抽象的部分

//既然是面向接口编程,我们先将可以抽象的对象和行为全部定义出来//定义 读物 的抽象类public abstract class AbstractBook{//定义实现类必须实现的抽象方法//对于读物研而言就是输出读物内容public abstract string GetContent();}//定义 负责陪孩子读书的人 的抽象类public abstract class AbstractPerson{//定义一个AbstractBook变量,这就是对依赖于抽象//无论AbstractBook的实现类如何变化,这里的代码是可以稳定运行的public AbstractBook abstractBook;public AbstractPerson(AbstractBook book){this.abstractBook = book;}public abstract void Read();}

然后定义出实现的部分

    public class Storybook : AbstractBook{public override string GetContent(){string content = "孙悟空大闹天宫";return content;}}public class CodingBook:AbstractBook{public override string GetContent(){string content = "二进制,ascii...";return content;}}public class Father : AbstractPerson{public Father(AbstractBook book) : base(book){}public override void Read(){Console.WriteLine($"{this.GetType().Name}开始给孩子讲{abstractBook.GetType().Name}啦!");Console.WriteLine($"正在读:{abstractBook.GetContent()}");}}

客户端调用:

        static void Main(string[] args){//爸爸给孩子讲故事书AbstractBook book=new Storybook();AbstractPerson father = new Father(book);father.Read();Console.ReadKey();}

我们现在来看看,使用依赖倒置原则后的代码,应对需求变化的能力如何?

需求变化1:需要爸爸给孩子读数学启蒙书了,我们只需新增如下代码:

    public class Mathsbook : AbstractBook{public override string GetContent(){string content = "认识数字,加减法...";return content;}}

客户端调用:

        static void Main(string[] args){//爸爸给孩子讲数学启蒙书AbstractBook book=new Mathsbook();AbstractPerson father = new Father(book);father.Read();Console.ReadKey();}

相较于之前的代码,只是将AbstractBook book=new Storybook();替换为了AbstractBook book=new Mathsbook(); 不仅改动的代码少,而且有了对应变化的能力。

需求变化2:爸爸不在家,今天需要妈妈来给孩子读故事书了,我们只需新增一个Mother类

    public class Mother : AbstractPerson{public Mother(AbstractBook book) : base(book){}public override void Read(){Console.WriteLine($"{this.GetType().Name}开始给孩子讲{abstractBook.GetType().Name}啦!");Console.WriteLine($"正在读:{abstractBook.GetContent()}");}}

客户端调用:

        static void Main(string[] args){AbstractBook book=new Storybook();AbstractPerson father = new Mother(book);father.Read();Console.ReadKey();}

高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。

在上面的案例中:
在传统代码中 Father 中使用到了Storybook,Father相对于Storybook是高层模块,Father依赖于Storybook,就是高层依赖低层;Father和Storybook都是细节,且它们之间的依赖是细节间的依赖,这就违背了依赖倒置原则;
因此我们定义了AbstractPerson和AbstractBook两个抽象,AbstractPerson 对于 读物的依赖,不再依赖于具体的实现类如Storybook,CodingBook等,这些属于细节,而是依赖它们抽象类AbstractBook,这就符合 高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节。
我们再来看看Father类

    public class Father : AbstractPerson{public Father(AbstractBook book) : base(book){}public override void Read(){Console.WriteLine($"{this.GetType().Name}开始给孩子讲{abstractBook.GetType().Name}啦!");Console.WriteLine($"正在读:{abstractBook.GetContent()}");}}

Father 类是一个细节,但是在Read 方法中使用了abstractBook.GetContent()抽象类的GetContent方法。这就是细节应该依赖抽象。

2. 案例2

在这里插入图片描述
在传统的三层架构中,层与层之间是相互依赖的,UI层依赖于BLL层,BLL层依赖于DAL层。分层的目的是为了实现“高内聚、低耦合”。传统的三层架构只有高内聚没有低耦合,层与层之间是一种强依赖的关系,这也是传统三层架构的一种缺点。如果低层发生变化,可能上面所有的层都需要去修改,而且这种传统的三层架构也很难实现团队的协同开发,因为上层功能取决于下层功能的实现,下面功能如果没有开发完成,则上层功能也无法进行。

当我们使用依赖倒置原则后,上面的依赖关系就变成了下图这种依赖关系。
在这里插入图片描述
我们知道,在传统的三层架构里面,UI层直接依赖于BLL层,BLL层直接依赖于DAL层;由于每一层都是依赖下一层的实现,所以说当下层发生变化的时候,它的上一层也要发生变化。
当我们使用依赖倒置原则后UI、BLL、DAL三层之间没有直接的依赖关系,而是依赖于接口。具体实现就是应先确定出接口,DAL层抽象出IDAL接口,BLL层抽象出IBLL接口,这样UI层依赖于IBLL接口,BLL实现IBLL接口,BLL层依赖于IDAL接口,DAL实现IDAL接口。


结语

希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。


参考资料:
C#编程:依赖倒置原则DIP
C#设计模式六大原则 - 依赖倒置
C# IoC学习笔记

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • MySQL的简单介绍
  • nuclei-快速漏洞扫描器【安装使用详解】
  • 鸿蒙AI功能开发【人脸活体验证控件】 机器学习-场景化视觉服务
  • 前端发版(发包)缓存,需要强制刷新问题处理
  • git版本控制的底层实现
  • Flink 实时数仓(四)【DWD 层搭建(二)流量域事实表】
  • enq: HW - contention事件来啦
  • fme从json中提取位置到kml中
  • react引入高德地图并初始化卫星地图
  • 【Go - 编译:浅尝辄止 】
  • 华为云全域Serverless技术创新:全球首创通用Serverless平台被ACM SIGCOMM录用
  • Android进阶之路 - 解决WebView加载H5时软键盘遮挡输入框问题
  • python的多线程
  • 自动化部署的艺术:Conda包依赖管理的终极指南
  • 从传统监控到智能化升级:EasyCVR视频汇聚平台的一站式解决方案
  • 【剑指offer】让抽象问题具体化
  • EOS是什么
  • Invalidate和postInvalidate的区别
  • Javascript Math对象和Date对象常用方法详解
  • Laravel 中的一个后期静态绑定
  • LeetCode刷题——29. Divide Two Integers(Part 1靠自己)
  • MQ框架的比较
  • react-core-image-upload 一款轻量级图片上传裁剪插件
  • sublime配置文件
  • 大型网站性能监测、分析与优化常见问题QA
  • 分类模型——Logistics Regression
  • 基于组件的设计工作流与界面抽象
  • 记录一下第一次使用npm
  • 浅谈Kotlin实战篇之自定义View图片圆角简单应用(一)
  • 一个6年java程序员的工作感悟,写给还在迷茫的你
  • 云栖大讲堂Java基础入门(三)- 阿里巴巴Java开发手册介绍
  • 在Docker Swarm上部署Apache Storm:第1部分
  • 3月7日云栖精选夜读 | RSA 2019安全大会:企业资产管理成行业新风向标,云上安全占绝对优势 ...
  • CMake 入门1/5:基于阿里云 ECS搭建体验环境
  • Python 之网络式编程
  • 我们雇佣了一只大猴子...
  • #《AI中文版》V3 第 1 章 概述
  • #快捷键# 大学四年我常用的软件快捷键大全,教你成为电脑高手!!
  • $refs 、$nextTic、动态组件、name的使用
  • (02)Cartographer源码无死角解析-(03) 新数据运行与地图保存、加载地图启动仅定位模式
  • (22)C#传智:复习,多态虚方法抽象类接口,静态类,String与StringBuilder,集合泛型List与Dictionary,文件类,结构与类的区别
  • (DenseNet)Densely Connected Convolutional Networks--Gao Huang
  • (Matalb分类预测)GA-BP遗传算法优化BP神经网络的多维分类预测
  • (MIT博士)林达华老师-概率模型与计算机视觉”
  • (多级缓存)缓存同步
  • (二)Eureka服务搭建,服务注册,服务发现
  • (翻译)terry crowley: 写给程序员
  • (附源码)python旅游推荐系统 毕业设计 250623
  • (附源码)springboot车辆管理系统 毕业设计 031034
  • (附源码)springboot社区居家养老互助服务管理平台 毕业设计 062027
  • (九)c52学习之旅-定时器
  • (图)IntelliTrace Tools 跟踪云端程序
  • (转)使用VMware vSphere标准交换机设置网络连接
  • .[hudsonL@cock.li].mkp勒索加密数据库完美恢复---惜分飞
  • .mkp勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复