Ninject Factory:根据参数创建合适的class

Ninject Factory: Create the appropriate class based on a parameter

我有3个类:

  1. class SqlQueryService : IQueryService
  2. class FileQueryService : IQueryService
  3. class NCRFileQueryService : FileQueryService

我已经创建了一个接口工厂:

public interface IQueryServiceFactory
{
    IQueryService Create(string connection);
}

在应用模块中:

Bind(typeof(IQueryService)).To(typeof(SqlQueryService)).Named("Data Source");         
Bind(typeof(IQueryService)).To(typeof(NCRFileQueryService)).Named("NCR File Source");
Bind(typeof(IQueryService)).To(typeof(FileQueryService)).Named("File Source");

Bind<IQueryServiceFactory>().ToFactory();

在我的应用程序中,我想根据如下参数创建三个 类 之一的实例:

IQueryService queryService =
    _queryServiceFactory.Create(_configuration.SelectedTPV.Connection);

可以吗?

*注意:我的应用程序是带有 .NET Framework 3.5 的 winforms 应用程序,因为它适用于旧 windows

我使用的Ninject版本是3.2.2.0,Ninject Extensions Factory的版本是3.2.1.0

您可以通过创建自定义实例提供程序然后绑定您的工厂来做到这一点:

this.Bind<IQueryServiceFactory>()
    .ToFactory(() => new UseFirstArgumentAsNameInstanceProvider());

请参阅文档中的 Treating the first factory method parameter as a name specifier

谢谢欧文!

我阅读了 wiki,但我没有意识到这种定制。

最后我决定添加一个 class 比如:

public class QueryServiceInstanceProvider : StandardInstanceProvider
{
    protected override string GetName(System.Reflection.MethodInfo methodInfo, object[] arguments)
    {
        string connection = arguments[0].ToString();

        return connection.Split('=')[0];
    }

    protected override Ninject.Parameters.IConstructorArgument[] GetConstructorArguments(System.Reflection.MethodInfo methodInfo, object[] arguments)
    {
        return base.GetConstructorArguments(methodInfo, arguments).Skip(1).ToArray();
    }
}

在应用程序模块中:

Bind(typeof(IQueryService)).To(typeof(SqlQueryService)).Named("Data Source");         
Bind(typeof(IQueryService)).To(typeof(NCRFileQueryService)).Named("NCR File Source");
Bind(typeof(IQueryService)).To(typeof(FileQueryService)).Named("File Source");

Bind<IQueryServiceFactory>().ToFactory(() => new QueryServiceInstanceProvider());

由于您提供给工厂的值不是 用户提供的 值,因此不需要使用以下内容使消费代码复杂化:

  • 那个配置值
  • 工厂抽象

消费者不应依赖 IQueryServiceFactory,而应简单地依赖 IQueryService。如何为消费者提供正确的实现取决于您的应用程序的需求,但有两种选择。

选项 1:配置值在启动时已知(在配置 DI 容器之前)

当启动时知道配置值时,在配置 DI 容器之前,这仅意味着您只需根据该值在容器中注册一个实现。

例如:

Bind(typeof(IQueryService),
    value == "Data Source" ? typeof(SqlQueryService) :
    value == "NCR File Source ? typeof(NCRFileQueryService) :
    value == "File Source" ? typeof(FileQueryService) :
    throw new InvalidOperationException(value));

选项 2:配置值在之后已知,或者可以在应用程序的生命周期内更改

即使配置值在启动时不固定或未知,仍然没有理由使用工厂抽象并让消费者依赖该配置值。这都可以通过创建代理隐藏在 IQueryService 抽象之后:

public class ConfigurationSelectorQueryServiceProxy : IQueryService
{
    private readonly IQueryService a;
    private readonly IQueryService b;
    private readonly IQueryService c;
    public ConfigurationSelectorQueryServiceProxy(
        SqlQueryService a, NCRFileQueryService b, FileQueryService c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }

    // IQueryService methods. Forward the call to one of the wrapped services
    public object SomeMethod(object args) => GetService().SomeMethod(args);

    // Helper methods
    private IQueryService GetService() =>
        // Read configuration value
        GetService(_configuration.SelectedTPV.Connection);

    private IQueryService GetService(string value) =>
        value == "Data Source" ? (this.a :
        value == "NCR File Source ? this.b :
        value == "File Source" ? this.c :
        throw new InvalidOperationException(value);        
}

ConfigurationSelectorQueryServiceProxy 代理实现可以注册为 IQueryService 并注入到消费者中。这样消费者就不必了解选择正确实施的复杂性。他们可以简单地使用 IQueryService 抽象。