【问题标题】:Factory pattern and lifetime of injected dependencies dilemma注入依赖困境的工厂模式和生命周期
【发布时间】:2018-01-20 05:46:04
【问题描述】:

这个问题困扰了我很久,一直没有找到正确的答案。

问题。

假设您有一个工厂接口(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();
    }
}

但是为什么我们只在Foo.Dispose() 上处理Qux? 这两个依赖项都是注入的,我们只依赖于工厂的确切实现知识,其中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,并且按需调用(延迟加载)也可能导致意外的延迟运行时错误(与快速失败相比)。

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

【问题讨论】:

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


    【解决方案1】:

    首先:

    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,我们需要为工厂的调用者提供处理Foo的方法

    正如in this article 所指出的,最优雅的方法是提供一个Release() 方法,允许调用者通知工厂实例已完成。然后由工厂实现决定是显式处理实例,还是委托给更高的权力(DI 容器)。

    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);
    }
    

    Release 方法保证 100% 的一次性用品将被正确处置,无论消费应用程序是使用 DI 容器还是使用纯 DI 连接。

    请注意,决定是否处理IFoo的是工厂。因此,当使用 DI 容器时,可以省略实现。但是,由于它应该多次调用Dispose() 是安全的,因此我们也可以将其保留在原处(并且可能使用布尔值来确保在有问题的组件上不会多次调用 dispose不要考虑这种可能性)。

    【讨论】:

    • 我喜欢泄漏抽象的想法,但我想更多地关注Qux 依赖项。我不同意上面的例子,因为我不想 (1) 过早地处理 _barService 和 (2) 再次,Foo 不拥有任何注入的依赖项,并且不能处理它们 - 这是一个不好的做法导致非常危险的结果。这是我最关心的问题,上面的例子仍然存在。是的,如果一个 DI 框架创建,它应该处置它。回到Qux - 每次都必须创建它并绑定到Foo 的生命周期。
    【解决方案2】:

    你每次都在更新 Qux,如果你能把它交给 DI 框架,那么你就不必调用 dispose,框架会在需要时工作

    【讨论】:

    • 让我澄清一下,Qux 必须每次都创建,并且它的生命周期与 Foo 的生命周期绑定。我不知道 DI 框架如何提供帮助。
    • 在我看来Qux 是运行时数据。运行时数据不是你应该让你的 DI Container 构造的东西,但你也不应该使用runtime data to construct components,因为这会导致你现在所处的确切情况,这会导致added complexity of factory abstractions
    • 感谢@Steven 的反馈,但不确定这是否有效。我将探讨如何将其映射到分布式服务和工作流(我问这个 SO 问题的原因),因为没有“分布式 IoC 容器”之类的东西。
    猜你喜欢
    • 2021-02-07
    • 2017-06-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多