使用Autofac实现依赖注射及Ioc

IocInverse of control)已经是叫嚷了很久的技术了,一直没有机会细看,最近因为看源代码的关系,研究了一点,拿出来分享一下。

当前网络上有很多Ioc的框架,比如说微软的企业库就使用Ioc技术重写了,还有Prism模式也用到了Ioc。我看的函数库是Autofac,但是理念跟其他的函数库大同小异,实际上,为了方便程序员在不同的Ioc框架上移植程序,各个框架的编写者开会定义了一个大家都支持的接口集:Common Service Locator

什么是Ioc

Ioc简言之,就是将类似下面创建对象的代码—我们称之为情况1

var checker = new MemoChecker(memos, new PrintingNotifier(Console.Out));

转换成下面这样—称之为情况2

var checker = container.Resolve<MemoCheck>();

container.Resolve<MemoCheck>这一行代码在创建MemoCheck这个类型的实例时,又可以通过下面的代码创建MemoCheck构造函数所需要的两个参数:

new MemoChecker(container.Resolve<IQueryable<Memo>>(),
                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实现依赖注入的基本步骤,下面是代码:


 1 using System;
 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 { getset; }
17         public DateTime DueAt { getset; }
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(20070312) },
62                 new Memo { Title = "Update CodeProject Article"
63                            DueAt = DateTime.Now },
64                 new Memo { Title = "Release Autofac 3"
65                            DueAt = new DateTime(20110701) }
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 


这个程序的作用是检查所有的记事项,提醒用户这些过期的记事项。这个程序里最主要的类是MemoCheckerMemoChecker需要两个对象才能构建一个实例—MemoIMemoDueNotifier。而这两个类型的对象,是由autofac自行解析的,autofac知道如何找到一个接口是由哪个对象实现的—这个过程叫做Resolve。而接口和实现接口对象的对映关系是由程序员在配置文件app.config,或者自己在程序的入口处(例如Main函数)注册好的—这个过程叫Register。因为实现接口的某些对象,有可能它的构造函数也会接受其他接口,而实现这些接口的对象也需要解析。因此,Autofac将所有的接口,和实现接口的对象都放到一个容器里,这个容器自己解析实现接口的对象之间的依赖关系—也就是ContainerBuilderContainerBuilderBuild的过程中,通过多次调用Resolve解决容器内部的对象依赖关系。当依赖关系都解析完毕以后,以后要创建对象,不需要再用类似下面的代码显式创建了:

var builder = new MemoChecker();


创建对象的工作,全部都交给Container解决,Container自己在内部找到构造对象时,Container创建调用构造函数要用到的参数的对象,解决对象之间的依赖关系,然后你只要用类似下面的代码就可以获取到你要的对象:

var builder = container.Resolve<MemoChecker>();

使用Autofac基于配置文件实现依赖注入

前面讲到的依赖注入,还是基于代码的,很多时候,使用Ioc和依赖注入技术,主要是为了支持插件技术。比如说,其他插件只要实现了定义的接口,那么,终端用户理论上可以只通过将实现插件的assembly拷贝到程序文件夹,并修改配置文件的形式来无缝集成新的插件。

那我们来看Autofac自带的例子—Calculator。这个程序有三个Assembly组成,Calculator是那个支持插件的程序;Calculator.Api包括了接口的定义,这样,Calculator和它的插件通过引用这个Assembly,就可以实现相互交互了;而Calculator.Operations就是最后实现接口的一些插件。

我们来看一看代码:

Calculator.Api定义了一个接口—这个接口将会被Calculator(支持插件的程序)和Calculator.Operations(插件)所使用:


 1 using System;
 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自动完成:

 1 using System;
 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文件里的具体设置:

 1 <?xml version="1.0"?>
 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读取配置文件里的接口与实现接口对象的映射关系。


 1 namespace Calculator
 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 


 

 

posted @ 2010-10-19 19:53  donjuan  阅读(11449)  评论(18编辑  收藏  举报