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 


     

     

  • 相关阅读:
    kebernet--新手填坑
    CentOS7下yum安装Kubernetes
    Centos 7 下部署Django + uWSGI + Nginx
    IPV4地址学习笔记
    【转】性能测试报告模板 V1.0
    【转载】Nginx简介及使用Nginx实现负载均衡的原理
    分布下 session 共享同步
    GC 收集算法及几种收集器优缺点讲解
    单线程集合和多线程集合
    在linux中实现多网卡的绑定 介绍常见的7种Bond模式
  • 原文地址:https://www.cnblogs.com/killmyday/p/1855799.html
Copyright © 2011-2022 走看看