这一直困扰着我很久,我找不到合适的答案。
问题。
想象一下,你有一个工厂界面(C#示例):
interface IFooFactory
{
IFoo Create();
}
它的实施取决于服务:
class FooFactory : IFooFactory
{
private readonly IBarService _barService;
public FooFactory(IBarService barService)
{
_barService = barService;
}
}
服务接口为正常关闭实现IDisposable
:
interface IBarService : IDisposable
{
...
}
现在工厂创建的实际类有2个依赖项 - 服务本身(通过工厂传递),以及工厂创建的另一个对象:
class Foo : IFoo
{
public Foo(IBarService barService, IQux qux)
{
...
}
}
并且工厂可以像这样创建它:
class FooFactory : IFooFactory
{
public IFoo Create()
{
IQux qux = new Qux();
return new Foo(_barService, qux);
}
}
最后IFoo
和IQux
同时实施IDisposable
,所以班级Foo
会这样做:
class Foo : IFoo
{
public void Dispose()
{
_qux.Dispose();
}
}
但为什么我们只在Qux
处理Foo.Dispose()
?
两个依赖项都被注入,我们只依赖于工厂的精确实现的知识,其中Bar
是共享服务(关联关系类型),而Qux
仅由Foo
使用(组成关系类型)。错误地处理它们都很容易。在这两种情况下Foo
逻辑上都不拥有任何依赖项,因此处理它们中的任何一个似乎都是错误的。在Qux
内部创建Foo
会否定依赖注入,因此它不是一种选择。
是否有更好的方法来拥有这两个依赖关系,并明确他们有什么样的关系来正确处理他们的生命?
可能的解决方案。
所以这里有一个可能不是那个漂亮的解决方案:
class FooFactory : IFooFactory
{
private readonly IBarService _barService;
public FooFactory(IBarService barService)
{
_barService = barService;
}
public IFoo Create()
{
// This lambda can capture and use any input argument.
// Also creation can be complex and involve IO.
var quxFactory = () => new Qux();
return new Foo(_barService, quxFactory);
}
}
class Foo : IFoo
{
public Foo(IBarService barService, Func<IQux> quxFactory)
{
// Injected - don't own.
_barService = barService;
// Foo creates - Foo owns.
_qux = quxFactory();
}
public void Dispose()
{
// Now it's clear what Foo owns from the code in the constructor.
_qux.Dispose();
}
}
我不喜欢在构造函数中调用可能复杂的逻辑,特别是如果它是async
,并且按需调用它(延迟加载)也会导致意外的后期运行时错误(快速失败)。
为了设计而走这么远真的有意义吗?无论如何,我想看看是否有其他可能的优雅解决方案。
答案 0 :(得分:2)
你每次都在新建Qux,如果你可以把它交给DI框架,那么你就不必调用dispose了,框架工作将在必要时
答案 1 :(得分:2)
首先:
interface IBarService : IDisposable
{
...
}
是 漏洞抽象 。我们只知道BarService
具有构造函数注入的一次性依赖项。这并不能保证IBarService
的每个实现都需要是一次性的。要删除漏洞抽象,IDisposable
应仅应用于实际需要它的具体实现。
interface IBarService
{
...
}
class BarService : IBarService, IDisposable
{
...
}
在处理依赖注入时,有一个register, resolve, release pattern正在发挥作用。此外,根据MSDN指南,创建一次性用品的一方也负责处理它。
一般来说,这意味着在使用DI容器时,DI容器负责实例化和处理实例。
但是, DI模式还允许在没有DI容器的情况下连接组件的可能性。因此,为了100%彻底,我们应该考虑设计具有一次性依赖性的组件。
简而言之就是:
Foo
一次性使用,因为它具有一次性依赖性Foo
,我们需要为工厂的调用者提供一种处理方式{{1 }} 正如in this article所指出的,最优雅的方法是提供一个Foo
方法,允许调用者通知工厂它已完成实例。然后由工厂实现决定是明确地处理实例,还是委托更高的功率(DI容器)。
Release()
然后工厂使用模式看起来像:
interface IFooFactory
{
IFoo Create();
void Release(IFoo foo);
}
class FooFactory : IFooFactory
{
private readonly IBarService _barService;
private readonly IQux qux;
public FooFactory(IBarService barService, IQux qux)
{
_barService = barService;
_qux = qux;
}
public IFoo Create()
{
return new Foo(_barService, _qux);
}
public void Release(IFoo foo)
{
// Handle both disposable and non-disposable IFoo implementations
var disposable = foo as IDisposable;
if (disposable != null)
disposable.Dispose();
}
}
class Foo : IFoo, IDisposable
{
public Foo(IBarService barService, IQux quxFactory)
{
_barService = barService;
_qux = quxFactory;
}
public void Dispose()
{
_barService.Dispose();
_qux.Dispose();
}
}
// Caller creates the instance, the caller owns
var foo = factory.Create();
try
{
// do something with foo
}
finally
{
factory.Release(foo);
}
方法保证100%的一次性用品将被妥善处理,无论消费应用是使用DI容器连接还是使用纯DI。
请注意, factory 决定是否处置Release
。因此,当使用DI容器时,可以省略实现。但是,因为应该可以安全地多次调用IFoo
,所以我们也可以将它保留到位(并且可能使用布尔值来确保在错误上不会多次调用dispose不允许的组件。