使用构造函数参数未知的Unity注入实例

Injecting an instance using Unity whose constructor parameter is not known

我有一个界面如下

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

实施

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

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

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

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

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

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

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

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

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

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

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

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

public interface ISourceContext
{
    string CurrentSource { get; }
}

此抽象允许检索当前上下文的源(无论上下文是什么,但通常是请求,例如 Web 请求)。

您的 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 我们将对 [=17= 的所有消费者施加额外的复杂性(额外的抽象) ],因为 IDataProvider 的所有消费者也需要使用 IDataProviderFactory

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