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

时间:2015-05-29 20:41:50

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

我有一个如下界面

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

实施

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

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

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

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

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

1 个答案:

答案 0 :(得分:4)

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

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

因此,我想到了两个解决方案。您可以更改GetData方法的合同,或者引入一个允许您检索上下文信息的抽象。

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

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

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

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

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我们会强加IDataProvider的所有消费者的额外复杂性(额外的抽象),因为IDataProvider的所有消费者都需要使用IDataProviderFactory

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