【问题标题】:ServiceContainer, IoC, and disposable objectsServiceContainer、IoC 和一次性对象
【发布时间】:2009-02-17 12:26:07
【问题描述】:

我有一个问题,我要标记这个主观的,因为我认为这就是它演变成的,更多的讨论。我希望有一些好的想法或一些发人深省的东西。对于这个冗长的问题,我深表歉意,但您需要了解上下文。

问题基本上是:

  • 您如何处理与 IoC 容器相关的具体类型?具体来说,谁负责处理它们,如果它们需要处理,以及这些知识如何传播到调用代码?

您是否要求它们是 IDisposable 的?如果不是,那么该代码是面向未来的,还是您不能使用一次性物品的规则?如果您在接口和具体类型上强制执行 IDisposable 要求以保证未来的发展,那么作为构造函数调用的一部分注入的对象是谁的责任?


编辑:我接受了@Chris Ballard 的回答,因为它与我们最终采用的方法最接近。

基本上,我们总是返回如下所示的类型:

public interface IService<T> : IDisposable
    where T: class
{
    T Instance { get; }
    Boolean Success { get; }
    String FailureMessage { get; } // in case Success=false
}

然后我们从 .Resolve 和 .TryResolve 返回一个实现此接口的对象,这样我们在调用代码中得到的总是相同的类型。

现在,实现此接口的对象IService&lt;T&gt; 是 IDisposable,并且应该始终被丢弃。是否应该释放 IService&lt;T&gt; 对象不是由解析服务的程序员决定的。

然而,这是关键部分,无论服务实例是否应该被释放,该知识都被烘焙到实现IService&lt;T&gt; 的对象中,所以如果它是一个工厂范围的服务(即每次调用 Resolve 结束一个新的服务实例),那么服务实例将在IService&lt;T&gt;对象被释放时被释放。

这也使得支持其他特殊作用域成为可能,例如池化。我们现在可以说我们需要最少 2 个服务实例,最多 15 个,通常是 5 个,这意味着每次调用 .Resolve 都会从可用对象池中检索一个服务实例,或者构造一个新实例。然后,当持有池化服务的IService&lt;T&gt; 对象被释放时,服务实例被释放回其池中。

当然,这让所有代码看起来像这样:

using (var service = ServiceContainer.Global.Resolve<ISomeService>())
{
    service.Instance.DoSomething();
}

但它是一种简洁的方法,并且无论使用的服务类型或具体对象如何,它都具有相同的语法,因此我们选择它作为可接受的解决方案。


以下是原始问题,供后人参考


冗长的问题来了:

我们使用了一个 IoC 容器,最近我们发现了问题所在。

在非 IoC 代码中,当我们想使用文件时,我们会使用这样的类:

using (Stream stream = new FileStream(...))
{
    ...
}

毫无疑问,该类是否具有有限资源,因为我们知道必须关闭文件,并且该类本身实现了 IDisposable。规则很简单,我们构造一个对象的每个类,实现 IDisposable,都必须被处理掉。无话可问。调用 Dispose 是否是可选的不是由这个类的用户决定的。

好的,继续迈向 IoC 容器的第一步。假设我们不希望代码直接与文件对话,而是通过一层间接。让我们将此类称为此示例的 BinaryDataProvider。在内部,该类使用的是一个流,它仍然是一个一次性对象,因此上面的代码将更改为:

using (BinaryDataProvider provider = new BinaryDataProvider(...))
{
    ...
}

这并没有太大变化。类实现IDisposable的知识还在,不问问题,我们需要调用Dispose。

但是,让我们假设我们的类提供的数据目前不使用任何此类有限资源。

上面的代码可以写成:

BinaryDataProvider provider = new BinaryDataProvider();
...

好的,到目前为止一切都很好,但问题的关键来了。假设我们想使用 IoC 容器来注入此提供程序,而不是依赖于特定的具体类型。

代码将是:

IBinaryDataProvider provider =
    ServiceContainer.Global.Resolve<IBinaryDataProvider>();
...

请注意,我假设有一个可用的独立接口,我们可以通过它访问对象。

通过上述更改,如果我们稍后想要使用一个真正应该被丢弃的对象怎么办?解析该接口的现有代码都没有编写来处理该对象,那么现在怎么办?

按照我们的看法,我们必须选择一种解决方案:

  • 实施运行时检查,检查是否正在注册的具体类型实现 IDisposable,要求它公开的接口也实现 IDisposable。这不是一个好的解决方案
  • 在对正在使用的接口进行约束之前,它们必须始终从 IDisposable 继承,以便面向未来
  • 强制运行时没有具体类型可以是 IDisposable,因为这不是由使用 IoC 容器的代码专门处理的
  • 只需让程序员检查对象是否实现 IDisposable 并“做正确的事”?
  • 还有其他人吗?

另外,在构造函数中注入对象呢?我们的容器以及我们研究过的其他一些容器能够将新对象注入到具体类型的构造函数的参数中。例如,如果我们的BinaryDataProvider 需要一个实现ILogging 接口的对象,如果我们对这些对象强制执行IDispose-“能力”,那么处置日志对象是谁的责任?

你怎么看?我想要意见,好的和坏的。

【问题讨论】:

    标签: inversion-of-control idisposable disposable


    【解决方案1】:

    一种选择可能是采用工厂模式,这样由 IoC 容器直接创建的对象永远不需要自己处理,例如

    IBinaryDataProviderFactory factory =
        ServiceContainer.Global.Resolve<IBinaryDataProviderFactory>();
    using(IBinaryDataProvider provider = factory.CreateProvider())
    {
        ...
    }
    

    缺点是增加了复杂性,但这确实意味着容器永远不会创建开发人员应该处理的任何东西 - 它始终是显式代码执行此操作。

    如果你真的想让它变得明显,工厂方法可以命名为 CreateDisposableProvider() 之类的东西。

    【讨论】:

    • IoC 容器的全部意义在于我以后通过配置可以返回不同的对象。在您的示例中,每个此类对象都必须实现 IDisposable。我的问题是:这是这些对象的标准吗?我可以在某种程度上指定这个标准,还是每个接口?面向未来?
    • 在这个例子中,我可以返回工厂的不同实现,但每个都必须创建一个实现 IDisposable 的提供程序。这并不理想,但至少可以为您提供有关语义的线索。也许这不是您要问的问题?
    • 不,我的问题是:我应该通过 IoC 容器强制所有此类接口和具体类型都实现 IDisposable,还是应该留给用户练习?
    • 我认为我们必须让开发人员负责 - IDisposable 就是告诉运行时我们已经完成了资源,而不是将其留给 GC。我们所能做的就是更清楚地表明我们正在创建一个需要显式释放的对象。
    【解决方案2】:

    (免责声明:我是根据 java 的东西来回答这个问题的。虽然我用 C# 编程,但我没有在 C# 中代理任何东西,但我知道这是可能的。对 java 术语感到抱歉)

    您可以让 IoC 框架检查正在构造的对象以查看它是否支持 一次性的。如果没有,您可以使用动态代理来包装 IoC 框架提供给客户端代码的实际对象。此动态代理可以实现 IDisposable,因此您始终可以将 IDisposable 传递给客户端。只要您使用的接口应该相当简单?

    那么您将遇到与开发人员通信的问题对象是 IDisposable。我不确定如何以一种好的方式完成。

    【讨论】:

    • 嗯,这听起来像“总是从 IoC 容器返回一个 IDisposable 对象”,然后最简单的解决方案就是将其作为具体类型的标准。该接口的实现当然可以留空,因此在代码方面没有太多开销,只有一种方法。
    • 是的,我想差别不大。我想基本问题是您需要将对象的可处置性传达给客户端代码,所以我认为希望不大;)
    • 我会选择不支持一次性物品。
    【解决方案3】:

    您实际上想出了一个非常肮脏的解决方案:您的IService 合同违反了SRP,这是一个很大的禁忌。

    我的建议是将所谓的“单例”服务与所谓的“原型”服务区分开来。 “单例”的生命周期由容器管理,容器可以在运行时查询特定实例是否实现了IDisposable,如果是,则在关闭时调用Dispose()

    另一方面,管理原型完全是调用代码的责任。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-10-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多