Ninject绑定T型的祖先

时间:2012-06-06 20:38:32

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

我有一个看起来像这样的依赖链:

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<>()看起来很有希望,但这只会在第一级有所帮助,这意味着如果我有CarSalesFtpFileProviderMotorcycleSalesFtpProvider,我可以这样做:

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

但是创建FtpFileProvider的两个具体实现似乎非常愚蠢,实际上只有我希望它们使用的设置不同。我看到有一个名为WhenAnyAnchestorNamed(string name)的方法。但是这条路线要求我在我的批处理作业上放置属性和魔术字符串,我并不感到兴奋。

我还注意到绑定语句有一个简单的旧.When(Func<IRequest, bool>)方法,所以我想出了这个作为我的绑定语句:

//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。

很抱歉有很长的背景说明。

以下是我的问题:我有什么理由不这样处理这个问题吗?这被认为是不好的形式吗?有没有更好的方法来查找祖先请求类型?我应该将我的类/依赖关系链重组为更“宜人”的方式吗?

3 个答案:

答案 0 :(得分:6)

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

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

答案 1 :(得分:4)

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

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>();

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

答案 2 :(得分:1)

我的建议是通过注册来定位违反惯例的目的地,而不是IFtpSettings本身。例如,在类似情况下,我会执行以下操作:

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

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

在向其他程序员解释如何实例化每个批处理作业时,这是非常简单的。而不是针对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容器中以在适当的情况下提供正确的类。