我有一个看起来像这样的依赖链:
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<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。
很抱歉有很长的背景说明。
以下是我的问题:我有什么理由不这样处理这个问题吗?这被认为是不好的形式吗?有没有更好的方法来查找祖先请求类型?我应该将我的类/依赖关系链重组为更“宜人”的方式吗?
答案 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容器中以在适当的情况下提供正确的类。