C# 依赖倒置原则(DIP)

目录

一,引子

1.1 传统的程序架构

1.2 依赖倒置

1.3 依赖倒置的作用

二,依赖注入


一,引子

1.1 传统的程序架构

在程序执行过程中,传统的程序架构如图:

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

1.2 依赖倒置

依赖倒置(DIP):Dependence Inversion Principle的缩写,是一种软件架构设计的原则(抽象概念)。依赖于抽象不依赖于细节。主要有两层含义:

  1. 高层次的模块不应该依赖低层次的模块,两者都应该依赖其抽象。
  2. 抽象不应该依赖于具体,具体应该依赖于抽象。

如何理解这两句话?

  • 第一句话:模块之间的依赖是通过抽象发生的,实现类之间不应该发生直接的依赖关系,他们的依赖关系应该通过接口或者抽象类产生。
  • 第二句话:举个例子,假如我们要写BLL层的代码,直接就去实现了功能,等到开发完成以后发现没有使用依赖倒置原则,这时候在根据实现类去写接口,这种是不对的,应该首先设计抽象,然后在根据抽象去实现,应该要面向接口编程。

 根据依赖倒置原则重新设计代码执行架构:

UI、BLL、DAL三层之间应该没有直接的依赖关系,都应该依赖于接口。首先应该先确定出接口,DAL层抽象出IDAL接口,BLL层抽象出IBLL接口,这样UI层依赖于IBLL接口,BLL实现IBLL接口。BLL层依赖于IDAL接口,DAL实现IDAL接口。如下图所示:

1.3 依赖倒置的作用

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

在传统的三层架构里面,仅仅增加一个接口层,我们就实现了依赖倒置,目的就是降低层与层之间的耦合。有了这样的接口层,三层架构才真正实现了“高内聚、低耦合”的思想。

二,依赖注入

依赖倒置原则是架构层面上的,那么如何在代码层面上实现呢?这就需要用到控制反转IOC

传统开发,上端依赖(调用/指定)下端对象,会有依赖,把对下端对象的依赖转移到第三方容器(工厂+配置文件+反射),能够程序拥有更好的扩展性,是DIP的具体实现方式,可以用来减低计算机代码之间的耦合度。

依赖注入DI 是控制反转的一种实现方式。

  1. 是实现IOC的手段和方法,就是能做到构造某个对象时,将依赖的对象自动初始化并注入 :
  2. 有三种注入方式:构造函数注入--属性注入--方法注入(按时间顺序):
  3. 构造函数注入用的最多,默认找参数最多的构造函数,可以不用特性,可以去掉对容器的依赖

下面通过一个例子,对上述专业名词进行解释:

父亲给孩子讲故事,只要给这个父亲一本书,他就可以照着这本书给孩子讲故事。

  • 我们下面先用最传统的方式实现一下,这里不使用任何的设计原则和设计模式。

首先定义一个Book类:

 public class Book{public string GetContent(){return "门前大桥下,游过一群鸭";}}

然后定义父亲Father类:

 public class Father{public void read(){Book book = new Book();Console.WriteLine("爸爸开始给孩子讲故事了:");Console.WriteLine(book.GetContent());}}

主程序调用:

  static void Main(string[] args){Father father = new Father();father.read();Console.ReadKey();}
//打印效果:
爸爸开始给孩子讲故事了:
门前大桥下,游过一群鸭

 我们来看看关系图:

可以看到:Father是直接依赖于Book类。

这时需求发生了变化,不给爸爸书了,给爸爸报纸,让爸爸照着报纸给孩子读报纸,这时该怎么做呢?按照传统的方式,我们这时候需要再定义一个报纸类,还要修改Father类。

但是需求在不断的变化,不管怎么变化,对于爸爸来说,他一直在读读物,但是具体读什么读物是会发生变化,这就是细节,也就是说细节会发生变化。但是抽象是不会变的。如果这时候还是使用传统的OOP思想来解决问题,那么会导致程序不断的在修改。

下面使用工厂模式来优化:

首先创建一个接口:

 public interface IReader{string GetContent();}

然后让Book类和NewsPaper类都继承自IReader接口

    public class Book:IReader{public string GetContent(){return "门前大桥下,游过一群鸭";}}public class NewsPaper : IReader{public string GetContent(){return "7月3日是地球上有记录以来最热的一天";}}

 之后创建一个工厂类:

public static class ReaderFactory{public static IReader GetReader(string readerType){if (string.IsNullOrEmpty(readerType)){return null;}switch(readerType){case "NewsPaper":return new NewsPaper();case "Book":return new Book();default:return null;}}}

里面方法的返回值是一个接口类型。最后在Father类里面调用工厂类:

public class Father{private IReader reader { get; set; }public Father(string readerName){reader = ReaderFactory.GetReader(readerName);}public void read(){Console.WriteLine("爸爸开始给孩子讲故事了:");Console.WriteLine(reader.GetContent());}}

 最后在主程序调用:

 static void Main(string[] args){Father father = new Father("NewsPaper");father.read();Console.ReadKey();}
//打印效果:
爸爸开始给孩子讲故事了:
7月3日是地球上有记录以来最热的一天

此时的依赖关系图:

这时Father已经和Book、Paper没有任何依赖了,Father依赖于IReader接口,还依赖于工厂类,而工厂类又依赖于Book和Paper类。这里实际上已经实现了控制反转。Father(高层)不依赖于低层(Book、Paper)而是依赖于抽象(IReader),而且具体的实现也不是由高层来创建,而是由第三方来创建(这里是工厂类)。但是这里只是使用工厂模式来模拟控制反转,而没有实现依赖的注入,依赖还是需要向工厂去请求。

再进一步优化代码 

 

 由于此时没有了工厂,我们还是需要在主程序调用中实例化具体的实现类。

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

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

相关文章

CRC Principle and Implementation Method(Java C)

CRC原理和程序实现方法1_哔哩哔哩_bilibili 其实原理很简单 但是我想了两个小时。。 收获的是原来一些复杂的运算都可以通过位运算来实现。 实现思路 public class CRC16Calculator {public static String CRC16(byte[] bytes) {int CRC 0x0000ffff;int POLYNOMIAL 0x0000a…

sklearn.preprocessing模块介绍

数据预处理 Binarizer: 二值化 用于将数值特征二值化。它将特征值与给定的阈值进行比较,并将特征值转换为布尔值(0 或 1),取决于特征值是否超过阈值 Binarizer(*, threshold0.0, copyTrue)参数: threshold&#xf…

day1-二分查找

二分查找 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。 示例 1: 输入: nums [-1,0,3,5,9,12], target 9 输出: 4 解…

Redis实战案例14-分布式锁的基本原理、不同实现方法对比以及基于Redis进行实现思路

1. 分布式锁基本原理 基于数据库的分布式锁:这种方式使用数据库的特性来实现分布式锁。具体流程如下: 获取锁:当一个节点需要获得锁时,它尝试在数据库中插入一个特定的唯一键值(如唯一约束的主键)&#xff…

优化chatGPT提示词的Prompts

你扮演一个专业的chatGPT提示词工程师,我将为您提供我的提示词,它用三个反引号分隔,请根据openai发布的提示词标准和优化技巧,改进和优化我的提示词,让chatGPT能够更好的理解。 我的第一个提示词是:“”“……

【Java基础】CAS (Compare And Swap) 操作

关于作者:CSDN内容合伙人、技术专家, 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 ,擅长java后端、移动开发、人工智能等,希望大家多多支持。 目录 一、导读二、概览三、使用场景四、原理五、优劣5.1 缺点&#xff1…

git merge 与 git rebase 的区别

文章目录 前言1、使用 merge2、使用 rebase总结 前言 首先我们要清楚,git merge 与 git rebase 处理的问题是一样的,这两个命令都用于把一个分支的变更整合进另一个分支,只不过他们达成同样目的的方式不同。 刚开始,已经存在一…

你不会还不知道什么是企业博客吧?

企业博客是指由企业或组织创建的在线平台,主要是用于发布与其业务、产品、行业和相关主题相关的文章、信息和观点。通过企业博客可以实现促进品牌推广、客户培养和业务发展等,对于企业发展有极其重要的作用。 企业博客的目的 1.提供有关企业产品和服务的…

vscode解决本地浏览器运行项目时的跨域问题-Live server

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 总结 最近在用face-api.js开发前端的实时人脸识别,加载已经训练好的tf模型,这一步需要加载模型json文件,但是本地测试的时候控制…

812. 打印数字

链接: 812. 打印数字 - AcWing题库 题目: 输入一个长度为 nn 的数组 aa 和一个整数 sizesize,请你编写一个函数, void print(int a[], int size), 打印数组 aa 中的前 sizesize 个数。 输入格式 第一行包含两个整数 nn 和 sizesize。 第二行包…

【tomcat】应用服务

准备环境 三台虚拟机 192.168.1.120 192.168.1.122 192.168.1.131 三台虚拟机关闭防火墙 、查看光盘 、检测yun创库 查看JDK是否安装 [rootlocalhost ~]# java -version openjdk version "1.8.0_161" //这是系统自带的rpm方式安装 OpenJDK Runtime Environment…

亚马逊云科技143项安全标准与合规性认证,帮助企业满足安全合规要求

在亚马逊云科技,为满足客户不断变化的需求,亚马逊云科技持续创新与迭代,设计的服务能帮助客户满足最严格的安全和合规性要求。针对安全相关工作,亚马逊云科技服务团队与Amazon Security Guardians云守护者项目密切配合&#xff0c…