【问题标题】:Dependency Injection - What If Dependency Lifetimes Are Shorter Than The Dependent Object?依赖注入 - 如果依赖生命周期比依赖对象短怎么办?
【发布时间】:2011-01-27 19:50:25
【问题描述】:

我意识到 DI 是一种非常灵活的设计模式,尽管我很难接受它作为我创建解耦代码的“灵丹妙药”。

原因如下:当依赖对象的生命周期比它注入的依赖项的生命周期长时会发生什么?

示例应用程序:我有一个 BusinessLogic 类,它在我的应用程序的生命周期内被实例化。此类需要DataContext 对象来执行数据库操作。我已经创建了一个抽象的DataContextFactory,它有两个实现:StaticDataContextFactory 和WebDataContextFactory。前者在应用程序的整个生命周期内维护一个 DataContext,而后者将为每个 HTTP 请求创建新的 DataContext。

示例中的问题: 如您所见,使用StaticDataContextFactory 时一切正常。但是,当使用 WebDataContextFactory 时,BusinessLogic 将失败,因为它被注入了 DataContext,一旦第一个请求完成,该 DataContext 就会过期/释放。

我的问题是:所有依赖对象的生命周期都必须小于或等于其依赖关系的生命周期吗?如果是这样,那么当实例化依赖类的代码不知道每个依赖项的生命周期时会发生什么?

【问题讨论】:

  • 我不确定尝试在应用程序的整个生命周期中保持面向工作单元的 DataContext 是否有效。也许您应该考虑让您的 BusinessLogic 类基于每个方法获取和处理数据上下文?或者更好的是,找到一种方法让您的 BusinessLogic 类不是静态的,而是由需要它的 Web 请求实例化...
  • 虽然我意识到这个特定的例子可以重做,但我仍然怀疑我会发现自己处于这样一种情况,即一个可靠对象的范围比它的一个依赖项更长,但不会意识到由于各种抽象的情况。简而言之:您对我最后一段中的问题的回答是“是”。
  • 我同意乔尔的第一句话。
  • 劳伦斯,你错过了重要的一点。您注入的对象可以在您需要时检索或实例化另一个对象(在您的情况下为 DataContext)。因此,“当前”属性或方法。
  • 感谢 cmets 伙计们,但也许我会分享一个真实的例子:在 Umbraco(一个 .NET CMS)中,实例化 DataContext 的成本相对较高。此外,由于 DataContext 在内部执行的各种缓存操作,因此通过反复重用 DataContext 可以获得显着的性能优势。此外,通过执行 Rushmore 优化等,更复杂的实现可能会走得更远(我离题了)。尽管如此:汇集这些对象有一个明显的优势。但是,这会带来上述复杂性。

标签: c# .net design-patterns dependency-injection inversion-of-control


【解决方案1】:

Spring 框架的 Web 集成使用代理和方面解决了这个问题。更长范围的对象被注入到更短范围的对象的代理中。每个代理都知道如何通过 HTTP 会话或 HTTP 请求(分别针对会话和请求范围的 bean)获取其较短范围委托的“当前”版本。

见http://static.springsource.org/spring/docs/2.5.x/reference/beans.html#beans-factory-scopes-other-injection

【讨论】:

  • 在深入研究本文之前,此解决方案是否涉及将依赖类与 Spring 耦合?另请参阅我对佩德罗回答的评论;我知道服务定位方法,但意识到这总是会导致您的大多数类与特定的 IoC 容器(在本例中为 Spring)耦合。
  • Lawrence,为你自己节省很多时间,并在你的类上实现一个“Current”方法。您的 Web 可以使用 HttpContext.Current.Items[] 来存储您的 DataContext。哦 - 并将我的帖子标记为答案!
  • 不,Lawrence,依赖类与 Spring 没有任何耦合。也不是依赖类。
  • 不幸的是,您的应用程序与 Spring 耦合。
【解决方案2】:

正如其他发帖人所指出的,对此有基于代理的解决方案。不过,我会把它放在“最后的手段”类别中。

您可以重构以消除这种不一致,我认为从长远来看,最终结果会更好。我对您的方案了解不多,但您可以考虑以下几点:

  1. 摆脱工厂,让容器注入DataContext,然后使用容器的生命周期控件来调整DataContext在不同环境下的生命周期

  2. 不要将BusinessLogic 组件设为单实例。如果您为每次使用创建一个新的,它自然会选择一个网络范围的DataContext(如果 DC 是这样配置的,或者是其他配置中的单个 DC)

  3. 如果BusinessLogic 有状态或实例化成本很高,请将成本高/有状态的部分移动到具有单实例生命周期的子组件中

我已经看到了可以在 Spring 中使用的基于代理的解决方案 - 这是个人喜好,但我对这个解决方案的长期可理解性持谨慎态度。您必须非常自律,以确保通过代理从“当前网络请求”返回的任何内容都不会被引用或保留的时间超过拥有它的请求...

在 IoC 中成功地使用生命周期确实很大程度上依赖于保持工作单元之间的清晰分离,这在 Web 环境中是愉快而自然的 - 如果可以的话,顺其自然是值得的。

希望这会有所帮助,尼克

【讨论】:

  • 好建议,谢谢尼克。澄清一下,你和乔尔的回答一样:对我帖子最后一段中的问题“是”。
  • 担心代理解决方案的可理解性是合理的。但这是一个实际的解决方案,它是我能想到的最安全和最透明的解决方案,除了重构您的代码以使 BusinessLogic 不是单例,这是 Nick 的 #2。 #1 听起来并不安全或更好,但也许尼克可以充实它。 #3 不是只是重申了这个问题吗?
  • 只是为了澄清代理解决方案:我会将 Func 注入到我的 BusinessLogic 中吗?这是我能想到的唯一不与特定容器耦合的代理。如果是这样,那么你在哪里停下来?难道所有的类都不应该这样写,因为它们永远无法确定依赖的生命周期吗?
  • 我提供的解决方案是特定于 Spring 的,尽管它 - 不会 - 将您的代码耦合到 Spring。我会用详细信息更新我的答案。
【解决方案3】:

我一直在思考一个类似的问题。 (查看我在 DI 上的帖子:Dependency Injection Container,第二个答案)我意识到使用接口注入的正确实现实际上可以解决问题。

我认为它可能运作良好的原因是,虽然从对象中提取接口很常见,但只要接口层次结构设计得当,即使对象超出范围,与接口相关的数据实际上会坚持下去。因此,在您的实际对象被使用它的代码释放之前,您最终不会拥有超出范围的依赖项。

在我展示的示例中,我推出了自己的“DI”,虽然它并不是真正的 DI 模式,但我相信它做得很好。

【讨论】:

  • " 如果对象超出范围,与接口相关的数据实际上将持续存在。" - 你确定吗?多态与否,您仍然引用了错误的对象。我使用“错误”一词是因为它可能仍在范围内,但已被释放到池中,您不应再引用它;相反,您应该获得不同的对象等。
  • 经过进一步研究,我发现以下内容:(msdn.microsoft.com/en-us/library/ms173156(v=VS.80).aspx) 接口类似于抽象基类。因此,只要使用了 Interface 实例,接口的实现者(实现它的类)就不能超出范围。
  • 对不起,bleepzter,但你错了。
  • @Pedro,你能详细说明一下吗?我很想知道我自己的学习。
  • 我相信 bleepzter 建议如果依赖项是从类中的成员变量引用的(通过其反射类型或基类型),那么它将保持在范围内。在这种情况下:是的,它将保持在范围内。只要它引用的 somewhere 它将始终保持在范围内。然而,这并不是说它没有被释放、释放到池中、被另一个线程重新获取等等。仅仅因为一个对象还没有被最终确定/GCd,并不意味着它仍然可以安全使用。
【解决方案4】:

为什么不能向 XXXDataContextFactory 询问“当前”数据上下文?您的静态工厂将始终返回同一个,而您的 Web 工厂将根据当前的 HttpRequest 返回一个。

【讨论】:

  • 这正是我以前做的。然而,这遵循了单例设计模式,该模式已经记录了缺陷;其中之一是您的整个应用程序只能拥有一个工厂,因为它必须静态存储。另一种方法是使用 IoC 容器并执行 Container.Resolve().Current。但是,我们现在正在执行服务定位而不是 DI,并且还将调用代码与特定容器(Spring、Unity 等)耦合。
  • 嗯?你标记为答案的帖子和我说的一样。我没有说任何关于单身人士的事情——根据你提出的问题,我使用的是你的术语。
  • 抱歉,如果我误解了,但是当您说“向 XXXDataContextFactory 询问当前数据上下文”时,这意味着(至少对我而言)我应该在抽象工厂类上创建一个静态 Current 方法。这是单例模式,无论是否实际维护单个实例(即可能是多例)。另一方面,Ladlestein 的回答(如果我没记错的话)建议将工厂方法(或“代理”)注入到可靠的类中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-07
  • 2017-06-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多