《设计模式》总结系列02: 依赖注入

1.前言

在讲《设计模式》前,还有一个实现技巧说一下。它就是依赖注入。

为什么要介绍它?

面向抽象(接口)编程是抓住“依赖倒置原则”(后续文章会介绍)  的核心。

依赖倒置是站在客户程序角度来看的,客户程序依赖的是“相对稳定”的接口,而不是“相对多的”子类。也就是客户程序不要依赖子类。

设计原则还有一个“里氏替换原则”,它是站在模式对象角度来看的,模式对象将“相对多变”的子类视同它的接口(或父类)。也就是父类出现的地方,子类可以代替。

说到“倒置”,可能晕乎。那么先说“正置”吧。依赖正置就是类间的依赖是实实在在的实现类间的依赖,也就是面向实现编程,也是人的通常的思维方式。我要使用电脑就得有电脑,依赖它。而编写程序需要的是对现实世界的事物进行抽象,这样就形成了抽象类或接口。系统设计一般依赖抽象,这样代替人的思维中的事物间的依赖,倒置也就产生了。

可是抽象的事物,是具体的事物的模板,终归于依靠具体实现的。该怎样使得客户程序不依赖的具体类型,可以使用依赖注入方式。

哎!解释得很绕了。抱歉!迷糊也罢。演示实例过程中再领悟啦。

2.依赖注入概述

客户程序依赖某个对象(或类),一般对它进行抽象,形成抽象类或接口,这样客户程序可以摆脱所依赖的具体类型。

依赖注入(Dependency Injection,简称DI),也可以叫控制反转(Inverse Of Control,简称IOC)。两种术语,本质是一样的。

3.案例场景

客户程序需要获取当前的时间。先创建一个时间提供者类,其代码:

    //时间提供者类
    public class TimeProvider
    {
        public DateTime CurrentDate
        {
            get { return DateTime.Now; }
        }
    }

在客户程序(Web程序)使用:

    [Route("api/[controller]")]
    public class TimeProviderController : Controller
    {
        //实例化
        TimeProvider tp = new TimeProvider();

        [HttpGet]
        public string Get()
        {
            return tp.CurrentDate.ToString();
        }
    }

这样客户程序依赖的是具体的TimeProvider类型了。我们进行抽象,并实现它。

创建接口:

    //时间提供者 接口类
    public interface ITimeProvider
    {
        DateTime CurrentDate { get; }
    }

实现:

    //时间提供者 实现类
    public class SystemTimeProvider : ITimeProvider
    {
        public DateTime CurrentDate
        {
            get { return DateTime.Now; }
        }
    }

客户程序:

    public class TimeProviderController : Controller
    {
        //实例化
        //TimeProvider tp = new TimeProvider();

        //tp变量表面类型是ITimeProvider,实际类型还是SystemTimeProvider
        //TODO:客户程序没有摆脱具体的SystemTimeProvider类型
        ITimeProvider tp = new SystemTimeProvider();

        [HttpGet]
        public string Get()
        {
            return tp.CurrentDate.ToString();
        }
    }

问题来了,客户程序该如何摆脱具体的SystemTimeProvider类型依赖?

4.反射方式

引入一个中间类,来装配类型:

    //装配类
    public class Assembler
    {
        //保存抽象类型与具体类型对应关系的字典
        static Dictionary<Type, Type> d = new Dictionary<Type, Type>();
        static Assembler()
        {
            //注册抽象类型需要的具体类型
            //TODO:可以通过配置文件来定义
            d.Add(typeof(ITimeProvider), typeof(SystemTimeProvider));
        }

        private static object Create(Type type)
        {
            if ((type == null) || !d.ContainsKey(type))
            {
                throw new NullReferenceException();
            }

            return Activator.CreateInstance(d[type]);
        }

        public static T Create<T>()
        {
            return (T)Create(typeof(T));
        }
    }

客户程序:

    public class TimeProviderController : Controller
    {

        //实例化
        //TimeProvider tp = new TimeProvider();

        //tp变量表面类型是ITimeProvider,实际类型还是SystemTimeProvider
        //TODO:客户程序没有摆脱具体的SystemTimeProvider类型
        //ITimeProvider tp = new SystemTimeProvider();

        //通过装配类的方法来实例化,这样摆脱了具体类型依赖
        ITimeProvider tp = Assembler.Create<ITimeProvider>();

        [HttpGet]
        public string Get()
        {
            return tp.CurrentDate.ToString();
        }
    }

客户程序只需依赖接口ITimeProvider和组配类Assembler,无须知道具体类型SystemTimeProvider的存在。其类图的静态结构:

注:Assembler相对于使用DI框架,功能弱爆了。当然,也可以使用工厂模式(后续文章介绍)来替代。

5.依赖注入框架

DI框架都是围绕一个容器对象构建的,当容器对象绑定到某些配置信息(或方法调用)时,它就会解析依赖性。

较为流行的DI框架:Autofac,CatleWindsor,Ninject,Sprint.Net,StructureMap,Untity,MEF等。

下面使用MVC6(EntityFramework7也有,或者单独引入)包含的DI框架:

在Startup.cs中ConfigureServices方法配置:

        public void ConfigureServices(IServiceCollection services)
        {
            //TODO:可以通过配置文件来定义

            //services.AddScoped<ITimeProvider, SystemTimeProvider>();

            //单例模式
            services.AddSingleton<ITimeProvider, SystemTimeProvider>();

            //services.AddInstance<ITimeProvider>(new SystemTimeProvider());

            services.AddMvc();
        }

6.构造方式

客户程序:

    public class TimeProviderController : Controller
    {
        private readonly ITimeProvider tp;
        public TimeProviderController(ITimeProvider _tp)
        {
            this.tp = _tp;
        }

        [HttpGet]
        public string Get()
        {
            return tp.CurrentDate.ToString();
        }
    }

7.设值方式

客户程序:

    public class TimeProviderController : Controller
    {
        //private readonly ITimeProvider tp;
        //public TimeProviderController(ITimeProvider _tp)
        //{
        //    this.tp = _tp;
        //}

        public ITimeProvider tp { get; set; }

        [HttpGet]
        public string Get()
        {
            return tp.CurrentDate.ToString();
        }
    }

代码格式是这样的,也就是不用构造函数。但目前ASP.NET5自带的DI还不能使用设值属性方式注入。

还用组配类来匹配,不用DI框架:

    public class TimeProviderController : Controller
    {
        public ITimeProvider tp { get; set; }

        //装配类来获取类型实例化,没有用到DI框架
        public TimeProviderController()
        {
            tp = Assembler.Create<ITimeProvider>();
        }

        [HttpGet]
        public string Get()
        {
            return tp.CurrentDate.ToString();
        }
    }

8.本章小结

关于依赖注入方式,还有接口注入方式,特性标注注入方式等,这些不常用,不扯了。

依赖注入也可以称为一个设计模式,但就象“前言”所说,当作实现技巧或者设计技巧而已。

常用的构造注入和设值注入方式,要掌握啦。两者区别?

构造注入是一次性的,当客户类型构造的时候就确定了。它适合那种生命周期不长,在其存续期间不需要重新适配的对象。

设值方式是对于生命周期较长的客户对象而言,可以在运行过程中随时注入,较为灵活。

9.附录

以上例子源码方案目录:

说明:

GiveCase.Modeling是建模设计

GiveCase.Web是表现层

GiveCase.Service是业务层

其下载地址,到 290576772  QQ群空间下载。

时间: 2024-07-31 14:35:22

《设计模式》总结系列02: 依赖注入的相关文章

asp.net core 系列 3 依赖注入

一. 依赖注入概述 在软件设计的通用原则中,SOLID是非常流行的缩略语,它由5个设计原则的首字母构成:单一原则(S).开放封闭原则(O).里氏替换原则(L).接口分离原则(I).依赖反转原则(D).本篇介绍依赖反转原则以及在ASP.NET Core中的实现. 直接依赖是指:当一个类需要另一个类协作来完成工作的时候就产生了依赖.举例比如:模块 A 调用模块 B 中的函数,而模块 B 又调用模块 C 中的函数,则编译时 A 取决于 B,而 B 又取决于 C.这是有严重的依赖关系,不属于松散耦合.

拥抱.NET Core系列:依赖注入(1)

依赖注入时编程手段中解耦和封装的一个非常重要的手段,我本人已经到了没有DI无法编写项目的程度了,在.NET Framework中微软并没有在FCL中引入DI,虽然推出了"Unity".而在.NET Core中DI几乎是所有组件的标配可见DI有多么的重要,本节主要简单介绍下微软在.NET Core中加入的DI组件. 前言 DIP.IoC.DI 说起DI不得不提IoC这个模式,很多人会把DI和IoC混为一谈,但其实这两者是概念和实现的关系. 依赖倒置原则(DIP):软件设计原则,要依赖于抽

会话EJB系列(六)依赖注入

在本文的开始之前,先给大家介绍一个概念"依赖". 什么是依赖呢? 简单的说,就是A组件需要调用B组件,B组件需要调用C组件,C组件需要调用D组件-..这种调用称为:依赖! 在最早的应用程序中,依赖关系时通过new关键字来实现的.A组件依赖B组件,就是在A组件中显示的new一个B组件.缺点: 1.硬编码,耦合性强,难以维护.A组件只需要B组件中的方法,不必关心B组件的创建问题! 2.频繁的创建对象,导致系统开销增大. 上面的这种缺点,直接催生了'工厂模式'. 在工厂模式中,A组件依赖B组

依赖注入[4]: 创建一个简易版的DI框架[上篇]

本系列文章旨在剖析.NET Core的依赖注入框架的实现原理,到目前为止我们通过三篇文章(<控制反转>.<基于IoC的设计模式>和< 依赖注入模式>)从纯理论的角度对依赖注入进行了深入论述,为了让读者朋友能够更好地理解.NET Core的依赖注入框架的设计思想和实现原理,我们创建了一个简易版本的DI框架,也就是我们在前面文章中多次提及的Cat.我们会上下两篇来介绍这个被称为为Cat的DI框架,上篇介绍编程模型,下篇关注设计实现.[源代码从这里下载] 目录一.DI容器的层

依赖注入和依赖注入容器

http://www.digpage.com/di.html#di 为了降低代码耦合程度,提高项目的可维护性,Yii采用多许多当下最流行又相对成熟的设计模式,包括了依赖注入(Denpdency Injection, DI)和服务定位器(Service Locator)两种模式. 关于依赖注入与服务定位器, Inversion of Control Containers and the Dependency Injection pattern 给出了很详细的讲解,这里结合Web应用和Yii具体实现

控制反转(IoC)与依赖注入(DI)

前言 最近在学习Spring框架,它的核心就是IoC容器.要掌握Spring框架,就必须要理解控制反转的思想以及依赖注入的实现方式.下面,我们将围绕下面几个问题来探讨控制反转与依赖注入的关系以及在Spring中如何应用. 什么是控制反转? 什么是依赖注入? 它们之间有什么关系? 如何在Spring框架中应用依赖注入? 什么是控制反转 在讨论控制反转之前,我们先来看看软件系统中耦合的对象.图1:软件系统中耦合的对象从图中可以看到,软件中的对象就像齿轮一样,协同工作,但是互相耦合,一个零件不能正常工

依赖注入与控制反转

抄自:http://blog.xiaohansong.com/2015/10/21/IoC-and-DI/ 找不到比这更清楚明白的了 场景:对象A依赖于对象B 控制反转: 控制反转前:由在类A中初始化B,对象A控制着对象B的初始化和使用, 控制反转后:对象B的初始化在对象A需要时由容器初始化并注入到对象A中,控制权在容器手上. 对象A对对象B的依赖,由主动变成了被动,控制权颠倒过来了,对象A与B解耦 依赖注入:就是将实例变量传入到一个对象中去.非自己主动初始化依赖,而通过外部来传入依赖的方式 前

yii依赖注入

为了降低代码耦合程度,提高项目的可维护性,Yii采用多许多当下最流行又相对成熟的设计模式,包括了依赖注入(Denpdency Injection, DI)和服务定位器(Service Locator)两种模式. 关于依赖注入与服务定位器, Inversion of Control Containers and the Dependency Injection pattern 给出了很详细的讲解,这里结合Web应用和Yii具体实现进行探讨,以加深印象和理解. 这些设计模式对于提高自身的设计水平很有

控制反转,依赖注入

最近在学习Spring框架,它的核心就是IoC容器.要掌握Spring框架,就必须要理解控制反转的思想以及依赖注入的实现方式.那么出现了以下问题 什么是控制反转? 什么是依赖注入? 它们之间有什么关系? 如何在Spring框架中应用依赖注入? 什么是控制反转 在讨论控制反转之前,我们先来看看软件系统中耦合的对象.图1:软件系统中耦合的对象从图中可以看到,软件中的对象就像齿轮一样,协同工作,但是互相耦合,一个零件不能正常工作,整个系统就崩溃了.这是一个强耦合的系统.齿轮组中齿轮之间的啮合关系,与软