我的编程空间,编程开发者的网络收藏夹
学习永远不晚

C#编程之依赖倒置原则DIP

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

C#编程之依赖倒置原则DIP

一、前言

我们先来看看传统的三层架构,如下图所示:

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

传统的三层架构没有遵循依赖倒置原则(DIP)来设计,所以就会出现上面的问题。

二、依赖倒置

依赖倒置(DIP):Dependence Inversion Principle的缩写,主要有两层含义:

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

我们先来解释第一句话:高层模块不应该直接依赖低层模块的具体实现,而是应该依赖于低层模块的抽象,也就是说,模块之间的依赖是通过抽象发生的,实现类之间不应该发生直接的依赖关系,他们的依赖关系应该通过接口或者抽象类产生。

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

我们在上面说过,在传统的三层架构里面没有使用依赖倒置原则,那么把依赖倒置原则应用到传统的三层架构里面会如何呢?我们知道,在传统的三层架构里面,UI层直接依赖于BLL层,BLL层直接依赖于DAL层,由于每一层都是依赖下一层的实现,所以说当下层发生变化的时候,它的上一层也要发生变化,这时候可以根据依赖倒置原则来重新设计三层架构。

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

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

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

依赖倒置原则是架构层面上的,那么如何在代码层面上实现呢?下面看控制反转。

三、控制反转

控制反转(IOC):Inversion of Control的缩写,一种反转流、依赖和接口的方式,它把传统上由程序代码直接操控的对象的控制器(创建、维护)交给第三方,通过第三方(IOC容器)来实现对象组件的装配和管理。

IOC容器,也可以叫依赖注入框架,是由一种依赖注入框架提供的,主要用来映射依赖,管理对象的创建和生存周期。IOC容器本质上就是一个对象,通常会把程序里面所有的类都注册进去,使用这个类的时候,直接从容器里面去解析。

四、依赖注入

依赖注入(DI):Dependency Injection的缩写。依赖注入是控制反转的一种实现方式,依赖注入的目的就是为了实现控制反转。

依赖注入是一种工具或手段,目的是帮助我们开发出松耦合、可维护的程序。

依赖注入常用的方式有以下几种:

  • 构造函数注入。
  • 属性注入。
  • 方法注入。

其中构造函数注入是使用最多的,其次是属性注入。

看下面的一个例子:父亲给孩子讲故事,只要给这个父亲一本书,他就可以照着这本书给孩子讲故事。我们下面先用最传统的方式实现一下,这里不使用任何的设计原则和设计模式。

首先定义一个Book类:

namespace DipDemo1
{
    public class Book
    {
        public string GetContent()
        {
            return "从前有座山,山上有座庙.....";
        }
    }
}

然后在定义一个Father类:

using System;

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

然后在Main方法里面调用:

using System;

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

我们来看看关系图:

我们看到:Father是直接依赖于Book类。

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

namespace DipDemo1
{
    public class NewsPaper
    {
        public string GetContent()
        {
            return "新闻";
        }
    }
}

这时依赖关系变了,因为爸爸要依赖于报纸了,这就导致还要修改Father类:

using System;

namespace DipDemo1
{
    public class Father
    {
        public void Read()
        {
            // 读书
            // Book book = new Book();
            //Console.WriteLine("爸爸开始给孩子讲故事了");
            //Console.WriteLine(book.GetContent());

            // 报纸
            NewsPaper paper = new NewsPaper();
            Console.WriteLine("爸爸开始给孩子讲新闻");
            Console.WriteLine(paper.GetContent());
        }
    }
}

假设后面需求又变了,又不给报纸了,换成杂志、平板电脑等。需求在不断的变化,不管怎么变化,对于爸爸来说,他一直在读读物,但是具体读什么读物是会发生变化,这就是细节,也就是说细节会发生变化。但是抽象是不会变的。如果这时候还是使用传统的OOP思想来解决问题,那么会导致程序不断的在修改。下面使用工厂模式来优化:

首先创建一个接口:

namespace DipDemo2
{
    public interface IReader
    {
        string GetContent();
    }
}

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

namespace DipDemo2
{
    public class Book : IReader
    {
        public string GetContent()
        {
            return "从前有座山,山上有座庙.....";
        }
    }
}

NewsPaper类:

namespace DipDemo2
{
    public class NewsPaper : IReader
    {
        public string GetContent()
        {
            return "王聪聪被限制高消费......";
        }
    }
}

然后创建一个工厂类:

namespace DipDemo2
{
    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类里面调用工厂类:

using System;

namespace DipDemo2
{
    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());
        }
    }
}

最后在Main方法里面调用:

using System;

namespace DipDemo2
{
    class Program
    {
        static void Main(string[] args)
        {
            Father father = new Father("Book");
            father.Read();

            Console.ReadKey();
        }
    }
}

我们这时候可以在看看依赖关系图:

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

下面继续优化代码,这里只需要修改Father类:

using System;

namespace DipDemo3
{
    public class Father
    {
        public IReader Reader { get; set; }

        /// <summary>
        /// 构造函数的参数是IReader接口类型
        /// </summary>
        /// <param name="reader"></param>
        public Father(IReader reader)
        {
            Reader = reader;
        }

        public void Read()
        {
            Console.WriteLine("爸爸开始给孩子讲故事了");
            Console.WriteLine(Reader.GetContent());
        }
    }
}

在Main方法里面调用:

using System;
namespace DipDemo3
{
    class Program
    {
        static void Main(string[] args)
        {
            var f = new Father(new Book());
            f.Read();

            Console.ReadKey();
        }
    }
}

如果以后换成了Paper,需要修改代码:

using System;
namespace DipDemo3
{
    class Program
    {
        static void Main(string[] args)
        {
            // Book
            //var f = new Father(new Book());
            //f.Read();

            // Paprer
            var f = new Father(new Paper());
            f.Read();

            Console.ReadKey();
        }
    }
}

由于这里没有了工厂,我们还是需要在代码里面实例化具体的实现类。如果有一个IOC容器,我们就不需要自己new一个实例了,而是由容器帮我们创建实例,创建完成以后在把依赖对象注入进去。

我们在来看一下依赖关系图:

下面我们使用Unity容器来继续优化上面的代码,首先需要在项目里面安装Unity,直接在NuGet里面搜索即可:

这里只需要修改Main方法调用即可:

using System;
using Unity;

namespace UnityDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            // 创建容器
            var container = new UnityContainer();

            // 扫描程序集、配置文件
            // 在容器里面注册接口和实现类,创建依赖关系
            container.RegisterType<IReader, Book>();

            // 在容器里面注册Father
            container.RegisterType<Father>();

            // 从容器里拿出要使用的类,容器会自行创建father对
            // 还会从容器里去拿到他所依赖的对象,并且注入进来
            // 
            var father = container.Resolve<Father>();

            // 调用方法
            father.Read();
            Console.ReadKey();
        }
    }
}

到此这篇关于C#编程之依赖倒置原则DIP的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持编程网。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

C#编程之依赖倒置原则DIP

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

C#中依赖倒置原则DIP的示例分析

这篇文章主要介绍C#中依赖倒置原则DIP的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!一、前言我们先来看看传统的三层架构,如下图所示:从上图中我们可以看到:在传统的三层架构中,层与层之间是相互依赖的,UI层
2023-06-29

python3 依赖倒置原则是什么

本篇内容介绍了“python3 依赖倒置原则是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!场景针对园区停车信息,需要对各个公司提供的停
2023-06-20

Java依赖倒置原则案例分析

今天小编给大家分享一下Java依赖倒置原则案例分析的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。定义依赖倒转原则,又称依赖倒
2023-06-29

C++依赖倒转原则和里氏代换原则有什么好处

设计模式(Designpattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。本篇介绍设计模式七大原则之一的依赖倒转原则
2023-02-25

C++依赖倒转原则和里氏代换原则的作用是什么

这篇“C++依赖倒转原则和里氏代换原则的作用是什么”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“C++依赖倒转原则和里氏代换
2023-07-05

java设计模式的依赖倒置原则怎么实现

这篇文章主要讲解了“java设计模式的依赖倒置原则怎么实现”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“java设计模式的依赖倒置原则怎么实现”吧!依赖倒置原则(Dependence Inv
2023-06-17

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录