这里的核心问题是您正在尝试使用运行时数据构建组件。当让您的容器构建您的对象图(由包含应用程序行为的应用程序组件组成)时,这些对象图应仅包含在编译时已知的信息,或在应用程序期间固定的信息(例如配置值)。当混合运行时数据时,事情开始迅速崩溃,因为您的 DI 配置开始迅速变得复杂,您的设计开始恶化(例如,因为您将开始添加工厂抽象)并且验证对象的正确性变得更加困难图表。
相反,应该使用方法调用(和属性调用)通过(已经存在的)对象图发送运行时数据。不使用构造函数,因为在对象构造过程中使用了构造函数。
于是想到了两个解决方案。要么更改GetData 方法的约定,要么引入允许您检索上下文信息的抽象。
更改GetData basically means that you pass that runtime value into theGetData`方法的契约:
public interface IDataProvider
{
List<string> GetData(string source);
}
这使得对GetData 的调用非常明确,如果所有IDataProvider 实现都需要该源,这是一个很好的解决方案。将IDataProviderFactory 与CreateProvider(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 一起使用。
此外,使用工厂仍然会使组件的构造复杂化,因为工厂需要在可能还需要其他(编译时)依赖项的组件的构造函数中使用运行时值。这更难实现,导致对象图的创建被延迟,并且更难以验证该图是否可以实际构建。与在应用程序启动后直接知道这一点(快速失败)相比,它会迫使您在运行时运行调用此工厂的实际代码以了解它是否有效。