![](https://i-blog.csdnimg.cn/blog_migrate/906ee274a13f61a5cdb86d6aba579730.jpeg)
上图以数据访问层为例,展示了Abstract Factory模式的应用。如图,现假设有针对Access和SQLServer两种数据库的数据访问层,它们都实现了数据访问层接口。每个数据访问层有自己的工厂,所有工厂都实现自IDALFactory接口。而客户类(这里就是业务逻辑层类)仅与工厂接口、数据访问层接口耦合,而与具体类无关,这样,只要通过配置文件确定实例化哪个工厂,就可以得到不同的数据访问层。
然而,这种设计虽然可行,但是代码比较冗余,因为这样需要为数据访问层的每一个实现编写一个工厂,业务逻辑层也一样。在以前,我们毫无办法,但是,.NET平台引入的反射机制,给我们提供了一种解决方案。使用反射,每个层只需要一个工厂,然后通过从配置文件中读出程序集的名称,动态加载相应类。另外,为了提高依赖注入机制的效率,这里引入缓存机制。下面来看具体实现。
配置 首先,需要在Web工程的Web.config文件的<appSettings>节点下添加如下两个项: <add key="DAL" value=""/> <add key="BLL" value=""/> 这两个配置选项分别存储要应用的数据访问和也业务逻辑层的程序集名称。value目前是空,是因为目前还没有各个层次的具体实现。
实现缓存操作辅助类 为实现缓存操作,我们将缓存操作封装成一个辅助类,放在Utility工程下,具体代码如下:
CacheAccess.cs:
CacheAccess
1using System;
2using System.Web;
3using System.Web.Caching;
4
5namespace NGuestBook.Utility
6{
7 /** <summary>
8 /// 辅助类,用于缓存操作
9 /// </summary>
10 public sealed class CacheAccess
11 {
12 /** <summary>
13 /// 将对象加入到缓存中
14 /// </summary>
15 /// <param name="cacheKey">缓存键</param>
16 /// <param name="cacheObject">缓存对象</param>
17 /// <param name="dependency">缓存依赖项</param>
18 public static void SaveToCache(string cacheKey, object cacheObject, CacheDependency dependency)
19 {
20 Cache cache = HttpRuntime.Cache;
21 cache.Insert(cacheKey, cacheObject, dependency);
22 }
23
24 /** <summary>
25 /// 从缓存中取得对象,不存在则返回null
26 /// </summary>
27 /// <param name="cacheKey">缓存键</param>
28 /// <returns>获取的缓存对象</returns>
29 public static object GetFromCache(string cacheKey)
30 {
31 Cache cache = HttpRuntime.Cache;
32
33 return cache[cacheKey];
34 }
35 }
36}
封装依赖注入代码 因为很多依赖注入代码非常相似,为了减少重复性代码,我们将可复用的代码先封装在一个类中。具体代码如下(这个类放在Factory工程下):
DependencyInjector.cs:
DependencyInjector
1using System;
2using System.Configuration;
3using System.Reflection;
4using System.Web;
5using System.Web.Caching;
6using NGuestBook.Utility;
7
8namespace NGuestBook.Factory
9{
10 /** <summary>
11 /// 依赖注入提供者
12 /// 使用反射机制实现
13 /// </summary>
14 public sealed class DependencyInjector
15 {
16 /** <summary>
17 /// 取得数据访问层对象
18 /// 首先检查缓存中是否存在,如果不存在,则利用反射机制返回对象
19 /// </summary>
20 /// <param name="className">数据访问类名称</param>
21 /// <returns>数据访问层对象</returns>
22 public static object GetDALObject(string className)
23 {
24 /** <summary>
25 /// 取得数据访问层名称,首先检查缓存,不存在则到配置文件中读取
26 /// 缓存依赖项为Web.Config文件
27 /// </summary>
28 object dal = CacheAccess.GetFromCache("DAL");
29 if (dal == null)
30 {
31 CacheDependency fileDependency = new CacheDependency(HttpContext.Current.Server.MapPath("Web.Config"));
32 dal = ConfigurationManager.AppSettings["DAL"];
33 CacheAccess.SaveToCache("DAL", dal, fileDependency);
34 }
35
36 /** <summary>
37 /// 取得数据访问层对象
38 /// </summary>
39 string dalName = (string)dal;
40 string fullClassName = dalName + "." + className;
41 object dalObject = CacheAccess.GetFromCache(className);
42 if (dalObject == null)
43 {
44 CacheDependency fileDependency = new CacheDependency(HttpContext.Current.Server.MapPath("Web.Config"));
45 dalObject = Assembly.Load(dalName).CreateInstance(fullClassName);
46 CacheAccess.SaveToCache(className, dalObject, fileDependency);
47 }
48
49 return dalObject;
50 }
51
52 /** <summary>
53 /// 取得业务逻辑层对象
54 /// 首先检查缓存中是否存在,如果不存在,则利用反射机制返回对象
55 /// </summary>
56 /// <param name="className">业务逻辑类名称</param>
57 /// <returns>业务逻辑层对象</returns>
58 public static object GetBLLObject(string className)
59 {
60 /** <summary>
61 /// 取得业务逻辑层名称,首先检查缓存,不存在则到配置文件中读取
62 /// 缓存依赖项为Web.Config文件
63 /// </summary>
64 object bll = CacheAccess.GetFromCache("BLL");
65 if (bll == null)
66 {
67 CacheDependency fileDependency = new CacheDependency(HttpContext.Current.Server.MapPath("Web.Config"));
68 bll = ConfigurationManager.AppSettings["BLL"];
69 CacheAccess.SaveToCache("BLL", bll, fileDependency);
70 }
71
72 /** <summary>
73 /// 取得业务逻辑层对象
74 /// </summary>
75 string bllName = (string)bll;
76 string fullClassName = bllName + "." + className;
77 object bllObject = CacheAccess.GetFromCache(className);
78 if (bllObject == null)
79 {
80 CacheDependency fileDependency = new CacheDependency(HttpContext.Current.Server.MapPath("Web.Config"));
81 bllObject = Assembly.Load(bllName).CreateInstance(fullClassName);
82 CacheAccess.SaveToCache(className, bllObject, fileDependency);
83 }
84
85 return bllObject;
86 }
87 }
88}
封装依赖注入代码 因为很多依赖注入代码非常相似,为了减少重复性代码,我们将可复用的代码先封装在一个类中。具体代码如下(这个类放在Factory工程下):
DependencyInjector.cs:
DependencyInjector
1using System;
2using System.Configuration;
3using System.Reflection;
4using System.Web;
5using System.Web.Caching;
6using NGuestBook.Utility;
7
8namespace NGuestBook.Factory
9{
10 /** <summary>
11 /// 依赖注入提供者
12 /// 使用反射机制实现
13 /// </summary>
14 public sealed class DependencyInjector
15 {
16 /** <summary>
17 /// 取得数据访问层对象
18 /// 首先检查缓存中是否存在,如果不存在,则利用反射机制返回对象
19 /// </summary>
20 /// <param name="className">数据访问类名称</param>
21 /// <returns>数据访问层对象</returns>
22 public static object GetDALObject(string className)
23 {
24 /** <summary>
25 /// 取得数据访问层名称,首先检查缓存,不存在则到配置文件中读取
26 /// 缓存依赖项为Web.Config文件
27 /// </summary>
28 object dal = CacheAccess.GetFromCache("DAL");
29 if (dal == null)
30 {
31 CacheDependency fileDependency = new CacheDependency(HttpContext.Current.Server.MapPath("Web.Config"));
32 dal = ConfigurationManager.AppSettings["DAL"];
33 CacheAccess.SaveToCache("DAL", dal, fileDependency);
34 }
35
36 /** <summary>
37 /// 取得数据访问层对象
38 /// </summary>
39 string dalName = (string)dal;
40 string fullClassName = dalName + "." + className;
41 object dalObject = CacheAccess.GetFromCache(className);
42 if (dalObject == null)
43 {
44 CacheDependency fileDependency = new CacheDependency(HttpContext.Current.Server.MapPath("Web.Config"));
45 dalObject = Assembly.Load(dalName).CreateInstance(fullClassName);
46 CacheAccess.SaveToCache(className, dalObject, fileDependency);
47 }
48
49 return dalObject;
50 }
51
52 /** <summary>
53 /// 取得业务逻辑层对象
54 /// 首先检查缓存中是否存在,如果不存在,则利用反射机制返回对象
55 /// </summary>
56 /// <param name="className">业务逻辑类名称</param>
57 /// <returns>业务逻辑层对象</returns>
58 public static object GetBLLObject(string className)
59 {
60 /** <summary>
61 /// 取得业务逻辑层名称,首先检查缓存,不存在则到配置文件中读取
62 /// 缓存依赖项为Web.Config文件
63 /// </summary>
64 object bll = CacheAccess.GetFromCache("BLL");
65 if (bll == null)
66 {
67 CacheDependency fileDependency = new CacheDependency(HttpContext.Current.Server.MapPath("Web.Config"));
68 bll = ConfigurationManager.AppSettings["BLL"];
69 CacheAccess.SaveToCache("BLL", bll, fileDependency);
70 }
71
72 /** <summary>
73 /// 取得业务逻辑层对象
74 /// </summary>
75 string bllName = (string)bll;
76 string fullClassName = bllName + "." + className;
77 object bllObject = CacheAccess.GetFromCache(className);
78 if (bllObject == null)
79 {
80 CacheDependency fileDependency = new CacheDependency(HttpContext.Current.Server.MapPath("Web.Config"));
81 bllObject = Assembly.Load(bllName).CreateInstance(fullClassName);
82 CacheAccess.SaveToCache(className, bllObject, fileDependency);
83 }
84
85 return bllObject;
86 }
87 }
88}
实现工厂
下面使用两个辅助类,实现数据访问层工厂和业务逻辑层工厂。
DALFactory.cs
DALFactory
1using System;
2using NGuestBook.IDAL;
3
4namespace NGuestBook.Factory
5{
6 /** <summary>
7 /// 数据访问层工厂,用于获取相应的数据访问层对象
8 /// 使用Abstract Factory设计模式+Facace设计模式+反射机制+缓存机制设计
9 /// </summary>
10 public sealed class DALFactory
11 {
12 /** <summary>
13 /// 获取管理员数据访问层对象
14 /// </summary>
15 /// <returns>管理员数据访问层对象</returns>
16 public static IAdminDAL CreateAdminDAL()
17 {
18 return (IAdminDAL)DependencyInjector.GetDALObject("AdminDAL");
19 }
20
21 /** <summary>
22 /// 获取留言数据访问层对象
23 /// </summary>
24 /// <returns>留言数据访问层对象</returns>
25 public static IMessageDAL CreateMessageDAL()
26 {
27 return (IMessageDAL)DependencyInjector.GetDALObject("MessageDAL");
28 }
29
30 /** <summary>
31 /// 获取评论数据访问层对象
32 /// </summary>
33 /// <returns>评论数据访问层对象</returns>
34 public static ICommentDAL CreateCommentDAL()
35 {
36 return (ICommentDAL)DependencyInjector.GetDALObject("CommentDAL");
37 }
38 }
39}
BLLFactory.cs
BLLFactory
1using System;
2using NGuestBook.IBLL;
3
4namespace NGuestBook.Factory
5{
6 /** <summary>
7 /// 业务逻辑层工厂,用于获取相应的业务逻辑层对象
8 /// 使用Abstract Factory设计模式+Facace设计模式+反射机制+缓存机制设计
9 /// </summary>
10 public sealed class BLLFactory
11 {
12 /** <summary>
13 /// 获取管理员业务逻辑层对象
14 /// </summary>
15 /// <returns>管理员业务逻辑层对象</returns>
16 public static IAdminBLL CreateAdminBLL()
17 {
18 return (IAdminBLL)DependencyInjector.GetBLLObject("AdminBLL");
19 }
20
21 /** <summary>
22 /// 获取留言业务逻辑层对象
23 /// </summary>
24 /// <returns>留言业务逻辑层对象</returns>
25 public static IMessageBLL CreateMessageBLL()
26 {
27 return (IMessageBLL)DependencyInjector.GetBLLObject("MessageBLL");
28 }
29
30 /** <summary>
31 /// 获取评论业务逻辑层对象
32 /// </summary>
33 /// <returns>评论业务逻辑层对象</returns>
34 public static ICommentBLL CreateCommentBLL()
35 {
36 return (ICommentBLL)DependencyInjector.GetBLLObject("CommentBLL");
37 }
38 }
39}
==========================================================
微软模式与实践小组发布了叫做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]】。作为一个快速的摘要,依赖注入容器就是一个用于构建高度松耦合的软件的工具。依赖注入容器处理相互关联对象的所有细节,因此你可以构建一个独立的组件。这对可测试性和灵活性方面有很大的影响。例如在一个银行系统的,你可以有一个对象,管理帐户转帐。要实现这个目标,需要获得个人账户的对象,再加上安全规则及审计方面的要求。 通常的实现看起来是这样的:
AccountTransfer
{
TransferMoney( sourceAccountNumber, destAccountNumber, amount)
{
Account sourceAccount = AccountDatabase.GetAccount(sourceAccountNumber);
Account destAccount = AccountDatabase.GetAccount(destAccountNumber);
sourceAccount.Withdraw(amount);
destAccount.Deposit(amount);
Logger.Write(, amount, sourceAccountNumber, destAccountNumber);
}
}
可以想象,这是相当可怕的代码(例如没有事务管理),虽然其可以正常工作。 ;-) 这是很简单的,但也是高度耦合的。全局的AccountDatabase 类调用意味着你甚至不能单独编译,更别提进行测试了。如果帐号是来自两个不同的银行会发生什么呢?同样的,全局的日志记录器意味着你必须先获得一个某个明确全局记录器类所创建日志记录器后,才能使用这个类。这一结果在当你尝试编写单元测试的时候是很痛苦的,而且从长远来看也极大地限制了灵活性。职责分离原则要求一个类不要有多个职责。在这里,这个类违反了这一原则,不仅关心如何转移资金的细节,而且还要知道如何从数据库中获取帐号和如何写日志信息。为了恢复灵活性,这些职责需要分离到不同的对象,然后在通过传递回这个对象来使用它们,看起来像这样:
AccountTransfer
{
IAccountRepository accounts;
ILogger logger;
AccountTransfer(IAccountRepository accounts, ILogger logger)
{
.accounts = accounts;
.logger = logger;
}
TransferMoney( sourceAccountNumber, destAccountNumber, amount)
{
Account sourceAccount = accounts.GetAccount(sourceAccountNumber);
Account destAccount = accounts.GetAccount(destAccountNumber);
sourceAccount.Withdraw(amount);
destAccount.Deposit(amount);
logger.Write(, amount, sourceAccountNumber, destAccountNumber);
}
}
这样更加封闭。现在我们不依赖于外部的全局对象,只是通过构造函数传递对象实例。这个类现在可以被单独测试,甚至可以通过简单地传入不同的IAccountRepository实现来和不同银行进行交互。不过,现在有了新的代价。AccountTransfer的创建者现在必须知道如何创建所依赖的对象。你使用哪个帐号数据库?那个日志记录器?如果这些都是通过配置来建立,例如现在你的代码依赖于配置并且重新设计。这就是依赖注入容器责无旁贷的,它是一个智能的对象工厂。你告诉容器如何解决特定对象的依赖关系。例如使用Unity,你可以像这样配置容器(使用API,也可以支持外部配置文件):
IUnityContainer container = UnityContainer();
container.RegisterType();
container.RegisterType();
这告诉容器”如果一个依赖于IAccountRepository实例的对象,就创建一个ContosoBankRepository实例并使用它,如果任何一个对象需要一个ILogger实例,就给它一个DatabaseLogger。”你现在可以像这样要求容器给你一个由依赖关系的实例对象:
container.Resolve ();
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