使用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 {  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 


这个程序的作用是检查所有的记事项,提醒用户这些过期的记事项。这个程序里最主要的类是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 


 

 

你可能感兴趣的:(auto)