zoukankan      html  css  js  c++  java
  • 使用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 


     

     

  • 相关阅读:
    LeetCode120 Triangle
    LeetCode119 Pascal's Triangle II
    LeetCode118 Pascal's Triangle
    LeetCode115 Distinct Subsequences
    LeetCode114 Flatten Binary Tree to Linked List
    LeetCode113 Path Sum II
    LeetCode112 Path Sum
    LeetCode111 Minimum Depth of Binary Tree
    Windows下搭建PHP开发环境-WEB服务器
    如何发布可用于azure的镜像文件
  • 原文地址:https://www.cnblogs.com/killmyday/p/1855799.html
Copyright © 2011-2022 走看看