扩展 ObjectBuilder:使依赖注入感知继承






4.43/5 (3投票s)
一篇关于扩展 Microsoft Patterns and Practices 组提供的 ObjectBuilder 的依赖注入框架的文章。
引言
本文(及相关代码)演示了如何扩展 Enterprise Library 中提供的依赖注入框架(具体来说,我的团队正在使用 Enterprise Library 的 2006 年 1 月 版本)。
背景
我正在领导一个开发团队,他们正在开发一个基于 Smart Client Software Factory (SCSF) 的新应用程序。使用 SCSF 允许我们松散地耦合应用程序的各个组件,并将它们的开发分配给不同的开发人员。
一个常见的任务(至少在我们的应用程序中!:))是在管理对话框中显示项目列表,然后在新建/编辑对话框中编辑选定的项目。创建和显示视图的模式看起来是这样的
WorkItem.Items.Add(View.SelectedItem, "NewEditCostCentre");
INewAndEditCostCentre view = WorkItem.SmartParts.AddNew<NewAndEditCostCentre>();
WorkItem.Workspaces["WorkspaceName"].Show(view);
视图演示器的构造函数如下所示
[InjectionConstructor]
public NewAndEditCostCentre([Dependency(Name = "NewEditCostCentre",
NotPresentBehavior = NotPresentBehavior.Throw,
SearchMode = SearchMode.Up)]CostCentre entity)
构造函数代码的问题在于,它实际上需要知道业务实体的具体类型而不是接口,这在一定程度上违背了 CAB“松散耦合”的精神。我真正希望在构造函数中看到的是这样的
[InjectionConstructor]
public NewAndEditCostCentre([Dependency(Name = "NewEditCostCentre",
NotPresentBehavior = NotPresentBehavior.Throw,
SearchMode = SearchMode.Up)]ICostCentre entity)
这样做的结果是,它根本不起作用。在上述设置下,您将收到一个 DependencyMissingException
,因为 DependencyResolver
无法找到 ICostCentre
。深入研究 DependencyResolver
,原因在于它获取 ID 的哈希码以及您想要注入的对象的类型,将它们进行 OR
运算,然后使用此哈希值有效地搜索集合中的对象。问题在于,当对象最初放入集合时,使用的哈希值是基于实现接口的类型的。
解决方案 1:生成更多 DependencyResolutionLocatorKeys
这是我提出的第一个解决方案。在仔细研究代码后,我认为可以使用一些反射来获取实现的接口和继承的类型,然后为它们全部生成并存储 DependencyResolutionLocatorKey
s,这样就可以解决问题了。而且,它很可能可以解决。然而,通过这个解决方案,我们开始遇到了 “意想不到的后果”定律。主要的后果是,我们突然生成了很多哈希键,而且我不知道原始哈希键的质量如何,这可能会以各种有趣的方式破坏依赖注入。(我们之所以使用相对陈旧的技术,其中一个原因是因为它仍然能让我们的客户领先于竞争对手,但让他们保持在“我们知道它有效的东西™”的舒适区。)
不过,对于感兴趣的人来说,似乎有四个地方需要进行更改
- 在 ObjectBuilder 本身,在
CreationStrategy.RegisterObject
中。 - CAB 中的三个位置
WorkItem.InitializeFields
WorkItem.LocateWorkItem
ServiceCollection.BuildFirstTimeItem
经过一番观察和思考,我打消了这个念头,又重新考虑了一下。
解决方案 2:修改 DependencyResolver 使其“更智能一点™”
通过一些巧妙的横向思维,Microsoft P&P 的某个人决定让用于依赖注入的属性负责解析自己的依赖项。对我来说,这意味着 DependecyAttribute
创建了 DependencyParameter
,后者被放入了 CAB 中待处理事项的大列表中。在策略的正确节点,处理过程会检索 DependencyParameter
并请求注入值。DependencyParameter
使用 DependencyResolver
来完成此操作。
那么,新的简单答案是,在解析器中加入一些代码,以便在基于哈希的查找失败时,它会遍历集合,查找具有正确 ID 的项,然后查看它们是否可以强制转换为所需的类型。而且,我也实现了这一点,并且奏效了。
但同样,我又遇到了 那条定律。客户要求我们做的另一件事是,确保我们尽可能合理地在我们维护的产品之间共享代码。显然,EL 和 CAB 非常适合这一点。然而,将我的代码放在 DependencyResolver
中会改变其行为,而其他项目中的其他人可能(无意中)依赖于此。因此,这个解决方案也和第一个一样,学会了跳伞(比喻意义上)。
间奏
这时,我已经花费了比正常情况长得多的时间来研究这个问题,而且已经是周五晚上的酒吧打烊时间了。我(在脑子里)搁置了这个问题,然后开始消磨我大脑的随机部分。正是在这个过程中,灵感闪现了!
解决方案 3:创建一个新的依赖注入属性
我意识到 Microsoft P&P 的某个人做了一个聪明的横向思维。他们让用于依赖注入的属性负责解析自己的依赖项。此时,我认为公平地说,解决方案二是通过数小时仔细而乏味的调试才弄清楚代码中实际发生的事情。因为,看着 DependancyAttribute
*等等*的定义,我实在无法理解 CAB 是如何将类联系起来的……啤酒!洞察力的带来者!
其余的几乎就是历史了。精明(且不耐烦)的人会注意到,ObjectBuilder.Extension 中的三个类与 ObjectBuilder 中的三个类几乎完全相同。我尝试过使用继承等方式来实现这些类,但不起作用,所以我回到了基础,复制粘贴并编辑了代码。
调用代码需要进行小小的更改才能正常工作
[InjectionConstructor]
public NewAndEditCostCentre([IADependency("NewEditCostCentre",
NotPresentBehavior = NotPresentBehavior.Throw,
SearchMode = SearchMode.Up)]ICostCentre entity)
新的属性是 IADependencyAttribute
,它有一个必需的参数,即要注入的项的 ID。除此之外,得益于一些比我聪明得多的人,ObjectBuilder 可以在不知道它的任何信息的情况下使用该属性及其支持类。
注意:您需要注意的一点是,该项目引用了通用的 Microsoft.Practices.ObjectBuilder.dll,该 DLL(通常)在安装 Enterprise Library 时会构建。这可能需要更改为对您实际使用的 DLL 的引用,特别是如果您已对其进行签名。
关注点
IADependencyResolver
类是这三个类中最有趣的。这是实际完成获取依赖值工作的地方。这是 ResolveBasedOnId
方法的主体
IReadableLocator locator = context.Locator;
object retVal = null;
// This loop is for if we're going up the parents of the list
while ((retVal == null) && (locator != null))
{
// Go through all the items in the locator
foreach (KeyValuePair<object, object> pair in locator)
{
// We're only interested in things that
// have a DependencyResolutionLocatorKey key
if (pair.Key is DependencyResolutionLocatorKey)
{
DependencyResolutionLocatorKey depKey =
(DependencyResolutionLocatorKey)pair.Key;
// See if the key's id and the id we're looking for match
if (object.Equals(depKey.ID, id))
retVal = IsResolvable(pair.Value, typeToResolve);
}
// Do a check to see if we've found
// our object (no point carrying on if we have!)
if (retVal != null) return retVal;
}
// See if we need to go up to the parent.
// This is done under the following conditions:
// - searchMode has been set to up
if (searchMode == SearchMode.Up)
locator = locator.ParentLocator;
else
locator = null;
}
// Return whatever we have (or haven't) found
return retVal;
IsResolvable
方法只是使用 Type
的 IsAssignableFrom
方法来查看是否可以进行强制转换。但我 99.9% 确定这可以做得更好。
历史
- 2008 年 7 月 17 日 - 初始版本。(抱歉篇幅较长。)