使用构造函数参数未知的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 the
GetData` 方法的约定:
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 值,而组件的构造函数可能还需要其他(编译时)依赖项.这更难实现,导致对象图的创建被延迟,并且更难验证这个图是否真的可以构建。它会迫使您 运行 在 运行 时间调用该工厂的实际代码,以查明它是否有效,而不是在您的应用程序启动后直接知道它(快速失败)。
我有一个界面如下
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 the
GetData` 方法的约定:
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 值,而组件的构造函数可能还需要其他(编译时)依赖项.这更难实现,导致对象图的创建被延迟,并且更难验证这个图是否真的可以构建。它会迫使您 运行 在 运行 时间调用该工厂的实际代码,以查明它是否有效,而不是在您的应用程序启动后直接知道它(快速失败)。