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

一、前言

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

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

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

二、依赖倒置

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

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

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

在来解释第二句话:接口或者抽象类不应该依赖于实现类。举个例子,假如我们要写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的缩写。依赖注入是控制反转的一种实现方式,依赖注入的目的就是为了实现控制反转。

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

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

  1. 构造函数注入。
  2. 属性注入。
  3. 方法注入。

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

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

首先定义一个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();
        }
    }
}

原文地址:https://www.cnblogs.com/dotnet261010/p/12289609.html

时间: 2024-08-01 02:50:19

C#编程:依赖倒置原则DIP的相关文章

Java 设计模式(十二) 依赖倒置原则(DIP)

依赖倒置原则(Dependence Inversion Principle) 依赖倒置原则(DIP)的基本概念 原始定义 高层模块不应该依赖低层模块,两者都应该依赖其抽象 抽象不应该依赖细节 细节应该依赖抽象 Java中的具体含义 模块间的依赖通过抽象发生 实现类之间不发生直接的依赖关系 其依赖关系通过接口或者抽象类产生 接口或抽象类不依赖于具体实现 实现类依赖接口或抽象类 依赖倒置(DIP)的好处 采用DIP可以减少类之间的耦合性,提高稳定性,降低并行开发带来的风险,提高代码的可读性和可维护性

对依赖倒置原则(DIP)及Ioc、DI、Ioc容器的一些理解

.概述 所谓依赖倒置原则(Dependence Inversion Principle)就是要依赖于抽象,不要依赖于具体.简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合,并由此引申出IoC.DI以及Ioc容器等概念. 2.意图 面向过程的开发,上层调用下层,上层依赖于下层,当下层剧烈变动时上层也要跟着变动,这就会导致模块的复用性降低而且大大提高了开发的成本. 面向对象的开发很好的解决了这个问题,一般情况下抽象的变化概率很小,让用户程序依赖于抽象,实现的细节

对依赖倒置原则(DIP)及Ioc、DI、Ioc容器的一些理解(转)

所谓依赖倒置原则(Dependence Inversion Principle)就是要依赖于抽象,不要依赖于具体.简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合,并由此引申出IoC.DI以及Ioc容器等概念. 面向过程的开发,上层调用下层,上层依赖于下层,当下层剧烈变动时上层也要跟着变动,这就会导致模块的复用性降低而且大大提高了开发的成本. 面向对象的开发很好的解决了这个问题,一般情况下抽象的变化概率很小,让用户程序依赖于抽象,实现的细节也依赖于抽象.即使

C#软件设计——小话设计模式原则之:依赖倒置原则DIP

前言:很久之前就想动笔总结下关于软件设计的一些原则,或者说是设计模式的一些原则,奈何被各种bootstrap组件所吸引,一直抽不开身.群里面有朋友问博主是否改行做前端了,呵呵,其实博主是想做“全战”,即各方便都有战斗力.关于设计模式,作为程序猿的我们肯定都不陌生.博主的理解,所谓设计模式就是前人总结下来的一些对于某些特定使用场景非常适用的优秀的设计思路,“前人栽树,后人乘凉”,作为后来者的我们就有福了,当我们遇到类似的应用场景的时候就可以直接使用了.关于设计模式的原则,博主将会在接下来的几篇里面

深入理解JavaScript系列(22):S.O.L.I.D五大原则之依赖倒置原则DIP

前言 本章我们要讲解的是S.O.L.I.D五大原则JavaScript语言实现的第5篇,依赖倒置原则LSP(The Dependency Inversion Principle ). 英文原文:http://freshbrewedcode.com/derekgreer/2012/01/22/solid-javascript-the-dependency-inversion-principle/ 依赖倒置原则 依赖倒置原则的描述是: A. High-level modules should not

敏捷软件开发:原则、模式与实践——第11章 DIP:依赖倒置原则

第11章 DIP:依赖倒置原则 DIP:依赖倒置原则: a.高层模块不应该依赖于低层模块.二者都应该依赖于抽象. b.抽象不应该依赖于细节.细节应该依赖于抽象. 11.1 层次化 下图展示了一个简单的层次化方案: 高层的Policy层使用了低层的Mechanism层,而Mechanism层又使用了更细节的Utility层.它存在一个隐伏的错误特征,那就是:Policy层对于其下一直到Utility层的改动都是敏感的.依赖关系是传递的. 下图展示了一个更为合适的模型: 每个较高层次都为它所需要的服

六大设计原则(三)DIP依赖倒置原则

原文:六大设计原则(三)DIP依赖倒置原则 依赖倒置原则DIP(Dependence Inversion Principle) 依赖倒置原则的含义 高层模块不能依赖低层模块,二者都应该依赖其抽象. 抽象不应该依赖于细节. 细节应该依赖抽象. 什么是高层模块?低层模块? 每一个原子逻辑就是低层模块,原子逻辑再组就是高层模块. 什么是抽象和细节? 抽象是抽象类,不可被实例化. 细节是实现类,比如实现的接口或继承抽象类的子类,可以被实例化. 表现在Java语言中就是面向接口编程 模块间的依赖是通过抽象

设计模式六大原则---依赖倒置原则(DIP)

定义 依赖倒置原则(Dependency Inversion Principle) 核心思想:依赖于抽象     具体体现: 体现一:高层模块不应该依赖低层模块.两个都应该依赖抽象. 体现二:抽象不应该依赖细节.细节应该依赖抽象. 依赖倒置原则告诉我们:细节是多变的,而抽象是相对稳定的.所以我们编程的时候要注重抽象的编程,而非细节编程. 实例 1.AGP插槽:主板和显卡之间的关系的抽象.主板和显卡通常是使用AGP插槽来连接的,这样,只要接口适配,不管是主板还是显卡更换,都不是问题. 2.驾照:司

敏捷软件开发 – DIP 依赖倒置原则

DIP 依赖倒置原则 高层模块不应该依赖于低层模块.二者都应该依赖于抽象. 抽象不应该依赖于细节.细节应该依赖于抽象. 依赖于低层模块的高层模块意味着什么?正是高层模块包含了应用程序中重要的策略选择和业务模型.这些高层模块使得其所在的应用程序区别于其他.然而,如果这些高层模块依赖于低层模块,那么对于低层模块的改动会直接影响到高层模块,从而迫使它们依次做出改动.如果高层模块独立于低层模块,那么高层模块就可以非常容易地被重用.该原则是框架设计的核心原则. 层次化 糟糕的层次关系. 更为适合的模型.每