11

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

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

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。

抱歉,背景解释太长了。

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

4

3 回答 3

6

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

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

于 2012-06-06T22:02:16.230 回答
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>();

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

于 2012-06-06T21:48:39.187 回答
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 容器在正确的情况下提供正确的类。

于 2012-06-07T15:41:04.493 回答