使用Autofac实现依赖注射及Ioc
Ioc(Inverse of control)已经是叫嚷了很久的技术了,一直没有机会细看,最近因为看源代码的关系,研究了一点,拿出来分享一下。
当前网络上有很多Ioc的框架,比如说微软的企业库就使用Ioc技术重写了,还有Prism模式也用到了Ioc。我看的函数库是Autofac,但是理念跟其他的函数库大同小异,实际上,为了方便程序员在不同的Ioc框架上移植程序,各个框架的编写者开会定义了一个大家都支持的接口集:Common Service Locator。
什么是Ioc
Ioc简言之,就是将类似下面创建对象的代码—我们称之为情况1:
转换成下面这样—称之为情况2:
而container.Resolve<MemoCheck>这一行代码在创建MemoCheck这个类型的实例时,又可以通过下面的代码创建MemoCheck构造函数所需要的两个参数:
container.Resolve<IMemoDueNotifier>())
情况2相对情况1的好处在于,在情况1 的代码里,程序员需要显式指定构建MemoChecker实例所要求的参数类型的实例。也就是说,MemoChecker在构造一个实例时,你需要显式传入第二个参数的具体实例(PrintingNotifier)。这样就导致一个问题,如果在后期程序发布以后,需要更换MemoCheck的第二个参数,那就只有修改程序代码一条路可走了。
针对于情况1的这个问题,那肯定有人会说,那就把MemoChecker构造函数的第二个参数定义成一个接口,然后在创建MemoChecker实例的时候,读一个配置文件,找到实现这个接口的具体类型,通过反射等机制创建对象传给MemoChecker的构造函数。这样就可以通过修改配置文件的方式,通过添加实现接口的插件,动态地修改程序的行为—这正是情况2所要做的,也就是Ioc和依赖注入(Dependence Injection)要解决的一个通用问题。
关于Ioc和依赖注入,网上已经有很多文章讲解这个概念了,有兴趣的朋友可以看看这篇文章,里面介绍的很详细:
http://martinfowler.com/articles/injection.html
使用Autofac实现依赖注入
我先以CodeProject的一个示例代码为例,讲解一下用Autofac实现依赖注入的基本步骤,下面是代码:
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.IO;
5 using Autofac;
6
7 namespace Remember
8 {
9 interface IMemoDueNotifier
10 {
11 void MemoIsDue(Memo memo);
12 }
13
14 class Memo
15 {
16 public string Title { get; set; }
17 public DateTime DueAt { get; set; }
18 }
19
20 class MemoChecker
21 {
22 readonly IList<Memo> _memos;
23 readonly IMemoDueNotifier _notifier;
24
25 public MemoChecker(IList<Memo> memos, IMemoDueNotifier notifier)
26 {
27 _memos = memos;
28 _notifier = notifier;
29 }
30
31 public void CheckNow()
32 {
33 var overdueMemos = _memos.Where(memo => memo.DueAt < DateTime.Now);
34
35 foreach (var memo in overdueMemos)
36 _notifier.MemoIsDue(memo);
37 }
38 }
39
40 class PrintingNotifier : IMemoDueNotifier
41 {
42 readonly TextWriter _writer;
43
44 public PrintingNotifier(TextWriter writer)
45 {
46 _writer = writer;
47 }
48
49 public void MemoIsDue(Memo memo)
50 {
51 _writer.WriteLine("Memo '{0}' is due!", memo.Title);
52 }
53 }
54
55 class Program
56 {
57 static void Main()
58 {
59 var memos = new List<Memo> {
60 new Memo { Title = "Release Autofac 1.1",
61 DueAt = new DateTime(2007, 03, 12) },
62 new Memo { Title = "Update CodeProject Article",
63 DueAt = DateTime.Now },
64 new Memo { Title = "Release Autofac 3",
65 DueAt = new DateTime(2011, 07, 01) }
66 };
67
68 var builder = new ContainerBuilder();
69 builder.Register(c => new MemoChecker(
70 c.Resolve<IList<Memo>>(), c.Resolve<IMemoDueNotifier>()));
71 builder.RegisterType<PrintingNotifier>().As<IMemoDueNotifier>();
72 builder.RegisterInstance(memos).As<IList<Memo>>();
73
74 builder.RegisterInstance(Console.Out)
75 .As<TextWriter>()
76 .ExternallyOwned();
77
78 using (var container = builder.Build())
79 {
80 container.Resolve<MemoChecker>().CheckNow();
81 }
82
83 Console.WriteLine("Done! Press any key.");
84 Console.ReadKey();
85 }
86 }
87 }
88
这个程序的作用是检查所有的记事项,提醒用户这些过期的记事项。这个程序里最主要的类是MemoChecker,MemoChecker需要两个对象才能构建一个实例—Memo和IMemoDueNotifier。而这两个类型的对象,是由autofac自行解析的,autofac知道如何找到一个接口是由哪个对象实现的—这个过程叫做Resolve。而接口和实现接口对象的对映关系是由程序员在配置文件app.config,或者自己在程序的入口处(例如Main函数)注册好的—这个过程叫Register。因为实现接口的某些对象,有可能它的构造函数也会接受其他接口,而实现这些接口的对象也需要解析。因此,Autofac将所有的接口,和实现接口的对象都放到一个容器里,这个容器自己解析实现接口的对象之间的依赖关系—也就是ContainerBuilder。ContainerBuilder在Build的过程中,通过多次调用Resolve解决容器内部的对象依赖关系。当依赖关系都解析完毕以后,以后要创建对象,不需要再用类似下面的代码显式创建了:
创建对象的工作,全部都交给Container解决,Container自己在内部找到构造对象时,Container创建调用构造函数要用到的参数的对象,解决对象之间的依赖关系,然后你只要用类似下面的代码就可以获取到你要的对象:
使用Autofac基于配置文件实现依赖注入
前面讲到的依赖注入,还是基于代码的,很多时候,使用Ioc和依赖注入技术,主要是为了支持插件技术。比如说,其他插件只要实现了定义的接口,那么,终端用户理论上可以只通过将实现插件的assembly拷贝到程序文件夹,并修改配置文件的形式来无缝集成新的插件。
那我们来看Autofac自带的例子—Calculator。这个程序有三个Assembly组成,Calculator是那个支持插件的程序;Calculator.Api包括了接口的定义,这样,Calculator和它的插件通过引用这个Assembly,就可以实现相互交互了;而Calculator.Operations就是最后实现接口的一些插件。
我们来看一看代码:
Calculator.Api定义了一个接口—这个接口将会被Calculator(支持插件的程序)和Calculator.Operations(插件)所使用:
2
3 namespace Calculator.Api
4 {
5 public interface IOperation
6 {
7 string Operator
8 {
9 get;
10 }
11
12 double Apply(double lhs, double rhs);
13 }
14 }
15
而在Calculator这个Assembly里,定义了一个Calculator这个类,枚举所有实现了IOperation的插件—这个枚举过程由Autofac自动完成:
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using Calculator.Api;
6
7 namespace Calculator
8 {
9 class Calculator
10 {
11 IDictionary<string, IOperation> _operations = new Dictionary<string, IOperation>();
12
13 public Calculator(IEnumerable<IOperation> operations)
14 {
15 if (operations == null)
16 throw new ArgumentNullException("operations");
17
18 foreach (IOperation op in operations)
19 _operations.Add(op.Operator, op);
20 }
21
22 public IEnumerable<string> AvailableOperators
23 {
24 get
25 {
26 return _operations.Keys;
27 }
28 }
29
30 public double ApplyOperator(string op, double lhs, double rhs)
31 {
32 if (op == null)
33 throw new ArgumentNullException("op");
34
35 IOperation operation;
36 if (!_operations.TryGetValue(op, out operation))
37 throw new ArgumentException("Unsupported operation.");
38
39 return operation.Apply(lhs, rhs);
40 }
41 }
42 }
43
请注意Calculator的构造函数,这个构造函数接受一个IEnumerable<IOperation>类型的参数,这个参数是autofac通过读取配置文件自动构建好一个实例,下面就是app.config文件里的具体设置:
2 <configuration>
3 <configSections>
4 <section name="calculator" type="Autofac.Configuration.SectionHandler, Autofac.Configuration"/>
5 </configSections>
6
7 <calculator defaultAssembly="Calculator.Api">
8 <components>
9 <component type="Calculator.Operations.Add, Calculator.Operations" member-of="operations"/>
10 <component type="Calculator.Operations.Multiply, Calculator.Operations" member-of="operations"/>
11
12 <component type="Calculator.Operations.Divide, Calculator.Operations" member-of="operations">
13 <parameters>
14 <parameter name="places" value="4"/>
15 </parameters>
16 </component>
17
18 </components>
19 </calculator>
20
21 </configuration>
22
在程序(Calculator)启动的时候,调用Autofac API里面的ContainerBuilder.RegisterModule来告诉Autofac读取配置文件里的接口与实现接口对象的映射关系。
2 {
3
4 static class Program
5 {
6 [STAThread]
7 static void Main()
8 {
9 try
10 {
11 var builder = new ContainerBuilder();
12
13 ...
14
15 builder.RegisterModule(new ConfigurationSettingsReader("calculator"));
16
17 ...
18 }
19 catch (Exception ex)
20 {
21 DisplayException(ex);
22 }
23 }
24 }
25 }
26