码迷,mamicode.com
首页 > 其他好文 > 详细

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

时间:2015-10-03 15:38:27      阅读:272      评论:0      收藏:0      [点我收藏+]

标签:

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群空间下载。

 

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

标签:

原文地址:http://www.cnblogs.com/givecase/p/4853269.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!