微软模式与实践小组发布了叫做 Unity 或者 Unity Application Block 的依赖注入容器。开发人员现在能够利用可扩展的轻量级容器创建松耦合应用。
InfoQ 有机会采访了 Unity 项目的开发领头人 Chris Tavares。
Rob Bazinet (RB): Chris 介绍一下你自己和你是如何参与 Unity 的?
Chris Tavares (CT): 我的名字叫 Chris Tavares。我是微软模式与实践小组的一名高级软件开发人员。我目前正在领导 Enterprise Library 4 和 Unity Application Block 的开发。我也写了大部分的 Unity 的代码,所以 Unity 的美丽都源于我的“错误”。我已经在模式与实践小组工作超过 2 年时间。在来微软工作之前,我从 90 年代开始从事工业软件外包,盒装软件开发,甚至一些嵌入式软件开发。
RB:Unity Application Block 是什么?
CT:Unity 是一个依赖注入(Dependency Injection,DI)容器。DI 的标准描述文章来自 Martin Flower[0]【译者注:中文译文参见 [4]】。作为一个快速的摘要,依赖注入容器就是一个用于构建高度松耦合的软件的工具。依赖注入容器处理相互关联对象的所有细节,因此你可以构建一个独立的组件。这对可测试性和灵活性方面有很大的影响。
例如在一个银行系统的,你可以有一个对象,管理帐户转帐。要实现这个目标,需要获得个人账户的对象,再加上安全规则及审计方面的要求。 通常的实现看起来是这样的:
<pre id="u3b5"> AccountTransfer <br id="cs.g"></br>{ <br id="h4eq"></br> TransferMoney( sourceAccountNumber, destAccountNumber, amount) <br id="tqys"></br> { <br id="ye.q"></br> Account sourceAccount = AccountDatabase.GetAccount(sourceAccountNumber); <br id="hvzq"></br> Account destAccount = AccountDatabase.GetAccount(destAccountNumber); <br id="k.4e"></br> sourceAccount.Withdraw(amount); <br id="rqc-"></br> destAccount.Deposit(amount); <br id="ka6t"></br> Logger.Write(, amount, sourceAccountNumber, destAccountNumber); <br id="wexp"></br> } <br id="z70r"></br>} <br id="ox.2"></br>
可以想象,这是相当可怕的代码(例如没有事务管理),虽然其可以正常工作。 ;-)
这是很简单的,但也是高度耦合的。全局的 AccountDatabase 类调用意味着你甚至不能单独编译,更别提进行测试了。如果帐号是来自两个不同的银行会发生什么呢?同样的,全局的日志记录器意味着你必须先获得一个某个明确全局记录器类所创建日志记录器后,才能使用这个类。这一结果在当你尝试编写单元测试的时候是很痛苦的,而且从长远来看也极大地限制了灵活性。
职责分离原则要求一个类不要有多个职责。在这里,这个类违反了这一原则,不仅关心如何转移资金的细节,而且还要知道如何从数据库中获取帐号和如何写日志信息。为了恢复灵活性,这些职责需要分离到不同的对象,然后在通过传递回这个对象来使用它们,看起来像这样:
<pre id="bcne"> AccountTransfer <br id="yt8l"></br>{ <br id="xtzo"></br> IAccountRepository accounts; <br id="lzd9"></br> ILogger logger; <p> AccountTransfer(IAccountRepository accounts, ILogger logger) </p><br id="h2kk"></br> { <br id="mr.l"></br>.accounts = accounts; <br id="ysmc"></br>.logger = logger; <br id="anm3"></br> } <p> TransferMoney( sourceAccountNumber, destAccountNumber, amount) </p><br id="xdam"></br> { <br id="c431"></br> Account sourceAccount = accounts.GetAccount(sourceAccountNumber); <br id="vbqp"></br> Account destAccount = accounts.GetAccount(destAccountNumber); <br id="ugb_"></br> sourceAccount.Withdraw(amount); <br id="vw_i"></br> destAccount.Deposit(amount); <br id="a..l"></br> logger.Write(, amount, sourceAccountNumber, destAccountNumber); <br id="grm."></br> } <br id="scot"></br>} <br id="veta"></br>
这样更加封闭。现在我们不依赖于外部的全局对象,只是通过构造函数传递对象实例。这个类现在可以被单独测试,甚至可以通过简单地传入不同的 IAccountRepository 实现来和不同银行进行交互。
不过,现在有了新的代价。AccountTransfer 的创建者现在必须知道如何创建所依赖的对象。你使用哪个帐号数据库?那个日志记录器?如果这些都是通过配置来建立,例如现在你的代码依赖于配置并且重新设计。
这就是依赖注入容器责无旁贷的,它是一个智能的对象工厂。你告诉容器如何解决特定对象的依赖关系。例如使用 Unity,你可以像这样配置容器(使用 API,也可以支持外部配置文件):
<pre id="w4re">IUnityContainer container = UnityContainer(); <br id="pv_i"></br>container.RegisterType(); <br id="nx__"></br>container.RegisterType(); <br id="xu.q"></br>
这告诉容器”如果一个依赖于 IAccountRepository 实例的对象,就创建一个 ContosoBankRepository 实例并使用它,如果任何一个对象需要一个 ILogger 实例,就给它一个 DatabaseLogger。”你现在可以像这样要求容器给你一个由依赖关系的实例对象:
<pre id="j7ne">container.Resolve<accounttransfer></accounttransfer>();
Resolve 方法的调用试图创建一个 AccountTransfer 实例。容器看到构造函数需要一个 IAccountRepository 和一个 ILogger 实例,因此它创建了那些对象(使用先前指定的特定类型)并通过构造函数传递给 AccountTransfer 实例。这是利用容器集中组织你的应用程序的方法。这在你的应用程序中提供了一个地方处理对象之间的挂接,并且释放了对象图上单独对象的构造。由此产生的灵活性无论是可测试性还是灵活性真的是非常值得。如果你的类的依赖关系发生改变,这并不影响对象的创建,只需要配置容器就可以。
RB:Unity 是 Enterprise Library 的一部分还是单独发布? 从我看过的资料来看,Microsoft Dependency Injection Block 是作为 Enterprise Library 4.0 的一部分来发布的。
CT:Unity 是单独发布的。Enterprise Library 4.0 是建立在 Unity 之上的,你可以通过 Unity 访问 Enterprise Library 的功能。
需要说明一点的是,在 Enterprise Library 2 和 Composite UI Application Block(CAB)发布的时候,这两个下面的引擎是一个叫做 ObjectBuilder 的类库。ObjectBuilder 是一个用来构建依赖注入容器的框架。CAB 和 Enterprise Library 都使用 ObjectBuilder,但是 OB 是自己独立的东西,后来被单独发布 [1]。
新版本的 ObjectBuilder 是 Unity 的一部分。Enterprise Library 4 还是和以前一样使用 ObjectBuilder:读取配置并建立适当的 Enterprise Library 对象。我们也引入了一种新的方式来访问 Enterprise Library 的功能,直接通过容器而不是把那种机制隐藏起来。Scott Densmore 的一篇博客 [2] 详细的描述我们正在计划的工作细节。
因此,再次重申:Unity 是作为一个独立的整体。Enterprise Library 使用了 Unity 的一部分或者是可通过 Unity 使用。为了节省下载的麻烦,Enterprise Library 包含 Unity 的二进制程序集。因此如果你关心的是在 Enterprise Library 中使用,已经为你准备好了,不需要安装额外的东西。
RB:在什么环境下,开发人员选择使用 Unity?
CT:第一个问题是你是否想使用依赖注入。如果是的话,我想 Unity 是一个很好的选择,但是我也建议你评估一下其他的容器。Scott Hanselman 列出了一些现.NET 依赖注入容器项目 [3]。
RB:Unity 和其他的依赖注入容器有什么不同以及和他们相比怎么样?
CT:从模式与实践上来说,在这个问题上我要非常小心。我不想给人这样的印象——不是赞同就是反对人们使用其他的项目。我们强烈建议大家评估自己的选择,并选择满足需要的最好的容器,不管是 Unity 还是现有的开源项目。
RB:已经有相当多的依赖注入容器,又是什么动机使得你们团队创建了 Unity?
CT:模式与实践一直围绕依赖注入提供指导有一段时间了。CAB、Mobile Client Software Factory、Smart Client Software Factory、Web Client Software Factory 和 Enterprise Library 都以“各种”方式使用依赖注入。最后一个词“各种”是致命的。虽然每个项目都建立在 ObjectBuilder 之上,使用依赖注入方式都是不同和不相容地。有一个明确的,功能齐全的容器对于我们围绕依赖注入提供更好的指导和基于容器的基础架构。
还有其他的原因,我们有的客户无论什么原因,不会去接触开放源代码的软件。拥有一个由微软提供支持的容器使得他们有更大的安全感,并让他们得到好处。如果他们将来选择使用其他的容器也使他们处于有利地位。
另一个目标是提高依赖注入容器在微软内外的使用。有一个微软提供的容器有助于依赖注入在广大微软.NET 社区和微软内部开发人员的使用。
RB:对于开发者和团队以最好的方式开始使用 Unity 有什么建议?
CT:抓紧下载 ,安装它并通读文档,并从我们已经发布的简单快速指南开始。
RB:有没有 Unity 快速指南和最佳实践的示例代码?
CT:我们已经有一个 Windows Forms 应用(红绿灯模拟器)小例子,利用容器注入服务。这实在是一个小型和相当容易的例子,包括 C#和 VB.NET 版本代码。
RB:Unity 有什么计划?
CT:没有什么事一成不变的,当然我个人的目标是增加一些特性(有能力拦截方法调用是列表中最高级别的),以及获得未来的模式与实践的资产以规范基于容器的基础架构。一个更好的文件配置系统。
从长远来看,我喜欢以合理的方式找一些或者所有这些概念都转换成核心平台。
RB:Chris,谢谢你接受我们的采访和这些 Unity 的重要信息。
从 Unity 的网站上看,Unity 是:
Unity Application Block (Unity) 是一个轻量级的, 可扩展的依赖注入容器. 它有助于构建松耦合的应用程序和为开发者提供以下便利:
- 简化对象的创建,特别在分层对象结构和依赖的情形下
- 它支持需求的抽象化,这允许开发人员在运行时或在配置文件中指定依赖,简化横切关注点(crosscutting concerns)的管理
- 它通过把组件配置推给容器来决定,增加了灵活性
- 服务定位能力 ; 这使客户端能够存储或缓存容器
开发者应看 Unity 介绍对 Unity 有个总体的了解开始学习 Unity。关于 Unity Application Block 的更多信息可参看模式与实践网站 ,并可以从 CodePlex 网站下载。读者可以到 Chris的博客。Enterprise Library 4.0 的依赖注入发布在InfoQ2007 年12 月份的文章叫做微软Enterprise Library 4.0 将支持依赖注入
[0] http://martinfowler.com/articles/injection.html
[1] http://www.codeplex.com/ObjectBuilder
[3] http://www.hanselman.com/blog/ListOfNETDependencyInjectionContainersIOC.aspx
[4] http://www.openbeta.cn/InversionOfControlContainersAndTheDependencyInjectionPattern.ashx
查看英文原文: Microsoft Unity Dependency Injection Application Block Released
评论