工厂模式和注入依赖性的生命周期困境

时间:2018-01-20 05:46:05

标签: c# oop dependency-injection factory-pattern object-lifetime

这一直困扰着我很久,我找不到合适的答案。

问题。

想象一下,你有一个工厂界面(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);
    }
}

最后IFooIQux同时实施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,并且按需调用它(延迟加载)也会导致意外的后期运行时错误(快速失败)。

为了设计而走这么远真的有意义吗?无论如何,我想看看是否有其他可能的优雅解决方案。

2 个答案:

答案 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%彻底,我们应该考虑设计具有一次性依赖性的组件。

简而言之就是:

  1. 我们需要使Foo一次性使用,因为它具有一次性依赖性
  2. 由于工厂的调用者负责实例化Foo,我们需要为工厂的调用者提供一种处理方式{{1 }}
  3. 正如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不允许的组件。