【问题标题】:Ninject Bind When Ancestor Of Type T当类型 T 的祖先时 Ninject 绑定
【发布时间】:2012-06-06 20:38:32
【问题描述】:

我有一个大致如下所示的依赖链:

public class CarSalesBatchJob
{
    public CarSalesBatchJob(IFileProvider fileProvider)
    { ... }
}

public class MotorcycleSalesBatchJob
{
    public MotorcycleSalesBatchJob(IFileProvider fileProvider)
    { ... }
}    

public class FtpFileProvider : IFileProvider
{
    public FtpFileProvider(IFtpSettings settings)
    { ... }
}

public class CarSalesFtpSettings : IFtpSettings { ... }
public class MotorcycleSalesFtpSettings : IFtpSettings { ... }

到目前为止,我一直在使用基于约定的绑定,但这还不够好,因为我已经为 IFtpSettings 提供了多个实现。所以我决定使用一些上下文绑定。乍一看,kernel.Bind<>().To<>().WhenInjectedInto<>() 看起来很有希望,但这只会在第一级有所帮助,这意味着如果我有一个 CarSalesFtpFileProvider 和一个 MotorcycleSalesFtpProvider 我可以这样做:

kernel.Bind<IFtpSettings>().To<CarSalesFtpSettings>()
    .WhenInjectedInto<CarSalesFtpFileProvider>();
kernel.Bind<IFtpSettings>().To<MotorcycleSalesFtpSettings>()
    .WhenInjectedInto<MotorcycleSalesFtpFileProvider>();

但是创建FtpFileProvider 的两个具体实现似乎很愚蠢,这实际上只是在我希望它们使用的设置上有所不同。我看到有一个方法叫WhenAnyAnchestorNamed(string name)。但是这条路线需要我在批处理作业中添加属性和魔术字符串,我对此并不感兴趣。

我还注意到绑定语句上有一个普通的旧 .When(Func&lt;IRequest, bool&gt;) 方法,所以我想出了这个作为我的绑定语句:

//at this point I've already ran the conventions based bindings code so I need to unbind
kernel.Unbind<IFtpSettings>();
kernel.Bind<IFtpSettings>().To<CarSalesFtpSettings>()
    .When(r => HasAncestorOfType<CarSalesBatchJob>(r));
kernel.Bind<IFtpSettings>().To<MotorcycleSalesFtpSettings>()
    .When(r => HasAncestorOfType<MotorcycleSalesBatchJob>(r));

// later on in the same class
private static bool HasAncestorOfType<T>(IRequest request)
{
    if (request == null)
        return false;

    if (request.Service == typeof(T))
        return true;

    return HasAncestorOfType<T>(request.ParentRequest);
}

因此,如果构造函数请求 IFtpSettings,我们会递归请求树以查看链中是否有任何请求的服务/类型与提供的类型(CarSalesBatchJob 或 MotorcycleSalesBatchJob)匹配,如果匹配则返回 true。如果我们一直到达链的顶部,则返回 false。

抱歉,背景解释太长了。

这是我的问题:我有什么理由不应该以这种方式解决问题?这被认为是不好的形式吗?有没有更好的方法来查找祖先请求类型?我应该将我的类/依赖链重组为更“合意”的方式吗?

【问题讨论】:

  • 我相信您在问题中确定的解决方案是实现两个将 FtpFileProvider 与 IFtpSettings 实现配对的具体类,是最容易理解/读取/调试/移交的解决方案。其他解决方案要求开发人员更多地了解您的特定 IOC 工具以及您如何配置它。此实现还遵循 Ninject 的约定优于配置。

标签: .net dependency-injection inversion-of-control ninject


【解决方案1】:

您应该使用request.Target.Member.ReflectedType 而不是request.Service 这是实现类型。

此外,WhenAnyAncestorNamed 不需要属性。您可以使用Named 方法标记您的作业的绑定。

【讨论】:

    【解决方案2】:

    这并不是你问题的真正答案,但你可以通过编写一个类来解决你的问题,如下所示:

    private sealed class FtpFileProvider<TFileProvider>
         : FtpFileProvider
        where TFileProvider : IFileProvider
    {
        public FtpFileProvider(TFileProvider settings)
            : base(settings) { }
    }
    

    在这种情况下,您的配置将如下所示:

    kernel.Bind<IFileProvider>()
        .To<FtpFileProvider<CarSalesFtpSettings>>()
        .WhenInjectedInto<CarSalesBatchJob>();
    
    kernel.Bind<IFileProvider>()
        .To<FtpFileProvider<MotorcycleSalesFtpSettings>>()
        .WhenInjectedInto<MotorcycleSalesBatchJob>();
    

    请注意,根据我的经验,我发现在大多数情况下,您认为需要基于上下文的注入,但实际上您的设计存在缺陷。但是,根据给定的信息,在您的情况下,我不可能对此发表任何看法,但您可能想看看您的代码。您可能能够以实际上不需要基于上下文的注入的方式重构您的代码。

    【讨论】:

      【解决方案3】:

      我的建议是在注册时针对违反约定的情况的目标,而不是 IFtpSettings 本身。例如,在类似的情况下,我会执行以下操作:

      container.Register<CarSalesBatchJob>(() => {
          ICommonSetting myCarSpecificDependency = container.Resolve<CarSpecificDependency>();
          new CarSalesBatchJob(myCarSpecificDependency);
      });
      
      container.Register<MotorcycleSalesBatchJob>(() => {
          ICommonSetting myMotorcycleSpecificDependency = container.Resolve<MotorcycleSpecificDependency>();
          new MotorcycleSalesBatchJob(myMotorcycleSpecificDependency);
      });
      

      在向其他程序员解释如何实例化每个 Batch Job 时,这几乎是直截了当的。不是针对 ICommonSetting 的注册来尝试处理每个一次性,而是在自己的情况下处理每个一次性。

      换句话说,想象一下如果这些类有两个依赖项需要在 IoC 容器中进行更改。您将在不同的地方有四行注册代码,但它们都是为了实例化 MotorcycleSalesBatchJob 或 CarSalesBatchJob 等。如果有人想了解如何引用该类,他必须寻找对类(或基类)的任何引用。为什么不直接编写代码来准确解释如何在一个地方实例化每一个?

      这样做的缺点(或者至少我从其他人那里听到的)是,如果这些具体类中的任何一个的构造函数发生更改,那么代码就会中断,您将不得不更改注册。嗯,对我来说,这是积极的,因为我已经在这种类型的道路上迈出了一步,IoC 容器会根据某种状态发生变化,我需要确保我仍然保持预期的行为。

      当您考虑各种可能性时,它会变得更加有趣。你可以这样做:

      container.Register<IProductCatalog>(() => {
          currentState = container.Resolve<ICurrentState>().GetTheState();
          if (currentState.HasSpecialPricing())
             return container.Resolve<SpecialPricingProductCatalog>();
          return container.Resolve<RegularPricingProductCatalog>();
      });
      

      事物在不同情况下可能如何工作的所有复杂性都可以分解为单独的类,留给 IoC 容器在正确的情况下提供正确的类。

      【讨论】:

        猜你喜欢
        • 2011-10-13
        • 1970-01-01
        • 2019-10-22
        • 1970-01-01
        • 1970-01-01
        • 2011-03-25
        • 2011-04-26
        • 1970-01-01
        • 2012-07-31
        相关资源
        最近更新 更多