【问题标题】:Injecting an instance using Unity whose constructor parameter is not known使用 Unity 注入其构造函数参数未知的实例
【发布时间】:2015-05-29 20:41:49
【问题描述】:

我有一个界面如下

public interface IDataProvider
{
     List<string> GetData();
}

它的实现

public class TextDataProvider: IDataProvider
{
    public TexDataProvider(string source){...}
    public List<string> GetData() {...}
}

我的一项服务使用 IDataProvider 来获取数据。可以通过更改 Unity Register 方法来注入不同的实现。

但是,构造函数的源参数只有在调用 GetData 时才知道。因此,当 Unity 注册 IDataProvider 的实现时,源参数是未知的。

我知道一种替代方法是将源移动到 GetData 方法,另一种是创建一个抽象工厂,其 CreateMethod 采用源参数并将其传递给 IDataProvider 实现构造函数。然后我们可以注入 Factory 而不是 IDataProvider 实例。

但是有没有更好的方法可以解决这个问题?

【问题讨论】:

    标签: design-patterns dependency-injection unity-container constructor-injection


    【解决方案1】:

    这里的核心问题是您正在尝试使用运行时数据构建组件。当让您的容器构建您的对象图(由包含应用程序行为的应用程序组件组成)时,这些对象图应仅包含在编译时已知的信息,或在应用程序期间固定的信息(例如配置值)。当混合运行时数据时,事情开始迅速崩溃,因为您的 DI 配置开始迅速变得复杂,您的设计开始恶化(例如,因为您将开始添加工厂抽象)并且验证对象的正确性变得更加困难图表。

    相反,应该使用方法调用(和属性调用)通过(已经存在的)对象图发送运行时数据。不使用构造函数,因为在对象构造过程中使用了构造函数。

    于是想到了两个解决方案。要么更改GetData 方法的约定,要么引入允许您检索上下文信息的抽象。

    更改GetData basically means that you pass that runtime value into theGetData`方法的契约:

    public interface IDataProvider
    {
        List<string> GetData(string source);
    }
    

    这使得对GetData 的调用非常明确,如果所有IDataProvider 实现都需要该源,这是一个很好的解决方案。将IDataProviderFactoryCreateProvider(string source) 方法一起使用也意味着所有实现都需要source 值,但是通过更改GetData 方法,我们阻止了使用额外的抽象层。

    另一方面,如果 source 是特定于实现的,或者可以更多地被视为上下文数据,则可以引入抽象。当前系统的时间和代表其执行操作的用户是上下文数据的示例。您不想通过系统从方法传递到方法。您只希望这些信息“可用”。这可以通过引入抽象来完成。例如:

    public interface ISourceContext
    {
        string CurrentSource { get; }
    }
    

    这种抽象允许检索当前上下文的源(无论上下文可能是什么,但通常是请求,例如网络请求)。

    您的TextDataProvider 实现现在可以依赖于这个新的ISourceContext 抽象:

    public class TextDataProvider : IDataProvider
    {
        public TexDataProvider(ISourceContext sourceContext){...}
        public List<string> GetData() {
            // Call CurrentSource 'at runtime'; never in the ctor.
            string source = this.sourceContext.CurrentSource;
            ...
        }
    }
    

    现在您可以为ISourceContext 提供某种特定于技术的适配器实现,允许为应用程序提供正确的源。例如:

    public sealed class AspNetSourceContext : ISourceContext {
        public string CurrentSource {
            get { return HttpContext.Current.Request.QueryString["source"]; }
        }
    }
    

    乍一看,这个解决方案看起来与使用工厂抽象相同,但有一些非常重要的区别。

    首先,只有TextDataProvider 实现知道新的抽象,其中IDataProviderFactory 将强制IDataProvider 的所有消费者增加额外的复杂性(额外的抽象),因为@987654340 的所有消费者@ 也需要与 IDataProviderFactory 一起使用。

    此外,使用工厂仍然会使组件的构造复杂化,因为工厂需要在可能还需要其他(编译时)依赖项的组件的构造函数中使用运行时值。这更难实现,导致对象图的创建被延迟,并且更难以验证该图是否可以实际构建。与在应用程序启动后直接知道这一点(快速失败)相比,它会迫使您在运行时运行调用此工厂的实际代码以了解它是否有效。

    【讨论】:

    • 很好的解释。谢谢
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多