依赖注入

1.背景

如果某个具体的(或类)对象被客户程序所依赖,通常把它们抽象成 抽象类或接口 。简单说,客户程序摆脱所依赖的具体类型,称之为 面向接口编程 。

那么问题来了?如何选择客户程序所需要的实现类?在使用 创建型 模式下创建对象是不难解决这个问题。

但如果设计的不是具体业务逻辑,而是公共类库或框架程序,对外只提供抽象而已,该如何把外部使用的类型传递给它们?

我们可以采用“ 依赖注入 ”的方式,将加工好的抽象类型实体“ 注入 ”到客户程序中。

注:依赖注入DI(Dependency Injection),它和控制反转IOC(Inversion of Control)是同一个意思,不同术语叫法。

2.场景

客户程序获取年份,我们先设计一个 接口 :

using System;

namespace Blog.Consoles
{
    public interface ITimeProvider
    {
        DateTime CurrentDate { get; }
    }
}

并对这个 接口实现 (可以多种实现哦,这也是设计接口的原因):

using System;

namespace Blog.Consoles
{
    public class TimeProvider : ITimeProvider
    {
        public DateTime CurrentDate
        {
            get { return DateTime.Now; }
        }
    }
}

那么我们在 客户程序使用 :

using System;

namespace Blog.Consoles
{
    class Program
    {
        static void Main(string[] args)
        {
            ITimeProvider tp = new TimeProvider();
            Console.WriteLine(tp.CurrentDate.Year);

            Console.ReadKey();
        }
    }
}

这样实现的 依赖关系 是:

显然这样客户程序还需要知道 具体类型 TimeProvider,我们增加一个对象:

using System;
using System.Collections.Generic;

namespace Blog.Consoles
{
    public class Assembler
    {
        //保存抽象类型和实体类型
        static Dictionary<Type, Type> d = new Dictionary<Type, Type>();

        static Assembler()
        {
            //注册抽象类型需要使用的实体类型
            d.Add(typeof(ITimeProvider), typeof(TimeProvider));
        }

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

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

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

此时 再改造依赖关系 :

这样客户程序只 依赖Assembler和ITimeProvider ,并不知道TimeProvider存在。

接下来如何写注入代码?下面分几种方式。

3.构造注入

构造注入使用构造方法,通过 Assembl er或其它机制把抽象类型作为参数传递。其实现代码:

using System;

namespace Blog.Consoles
{
    class Program
    {
        public ITimeProvider _tp;
        public Program(ITimeProvider tp)
        {
            _tp = tp;
        }

        public int GetYear()
        {
            return _tp.CurrentDate.Year;
        }

        static void Main(string[] args)
        {
            ITimeProvider time = new Assembler().Create<ITimeProvider>();
            Program p = new Program(time);
            Console.WriteLine(p.GetYear()); 

            Console.ReadKey();
        }
    }
}

4.设置注入

通过属性方法赋值实现的,相对于构造方法一次性注入的方式,设置注入可以在需要时有更改的机会。实现代码:

using System;

namespace Blog.Consoles
{
    class Program
    {
        public ITimeProvider _tp { get; set; }

        public int GetYear()
        {
            return _tp.CurrentDate.Year;
        }

        static void Main(string[] args)
        {
            ITimeProvider time = new Assembler().Create<ITimeProvider>();

            Program p = new Program();
            p._tp = time;

            Console.WriteLine(p.GetYear());

            Console.ReadKey();
        }
    }
}

还可以简写:

using System;

namespace Blog.Consoles
{
    class Program
    {
        public ITimeProvider _tp { get; set; }

        public int GetYear()
        {
            return _tp.CurrentDate.Year;
        }

        static void Main(string[] args)
        {
            var p = new Program() { _tp = new Assembler().Create<ITimeProvider>() };

            Console.WriteLine(p.GetYear());

            Console.ReadKey();
        }
    }
}

5.MEF注入

导出部件:

下面 不使用Assembler 对象,实现代码:

using System;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;

namespace Blog.Consoles
{
    class Program
    {
        private static CompositionContainer container;

        private void Compose()
        {
            var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
            container = new CompositionContainer(catalog);
            //将部件和宿主程序添加到组合容器
            container.ComposeParts(this, new TimeProvider());
        }

        //导入部件
        [Import]
        public ITimeProvider _tp { get; set; }

        public int GetYear()
        {
            return _tp.CurrentDate.Year;
        }

        static void Main(string[] args)
        {
            var p = new Program();
            p.Compose();
            
            Console.WriteLine(p.GetYear());

            Console.ReadKey();
        }
    }
}

6.ASP.NET 5注入

修改 Startup.cs :

测试:

7.小结

关于依赖注入的方式,还有接口注入,自定义特性等(它们之间区别和选择,自行体会或查资料),不常用就不说了。

感受到 ASP.NET 5自带DI爽 就行啦!(ASP.NET 5真的海皮啦!)

后面的帖子,进入项目实战了,最近比较忙,请园友保持耐心。