C# 依赖注入副作用(两步初始化反模式)?
我正在从事一个项目,其中我的构造函数只包含行为依赖项。i、 e.我从不传递值/状态 例如:C# 依赖注入副作用(两步初始化反模式)?,c#,dependency-injection,dependencies,C#,Dependency Injection,Dependencies,我正在从事一个项目,其中我的构造函数只包含行为依赖项。i、 e.我从不传递值/状态 例如: class ProductProcessor : IProductProcessor { public double SomeMethod(){ ... } } class PackageProcessor { private readonly IProductProcessor _productProcessor; private double _taxRate; public
class ProductProcessor : IProductProcessor
{
public double SomeMethod(){ ... }
}
class PackageProcessor
{
private readonly IProductProcessor _productProcessor;
private double _taxRate;
public PackageProcessor(IProductProcessor productProcessor)
{
_productProcessor = productProcessor;
}
public Initialize(double taxRate)
{
_taxRate = taxRate;
return this;
}
public double ProcessPackage()
{
return _taxRate * _productProcessor.SomeMethod();
}
}
为了传递状态,决定包括第二步(调用初始化)
我知道我们可以将其配置为IoC容器配置类中的命名参数,但是,我们不喜欢在配置文件中创建“新的namedParameter(paramvalue)”的想法,因为这会使其不必要地不可读,并会造成未来的维护痛点
我在不止一个地方见过这种模式
问题:我读了一些认为这两步初始化反模式。如果这是共识,那么这是否意味着通过IoC容器进行依赖注入的方法存在某种限制/弱点
编辑:
在查看了Mark Seeman的:
关于这个问题的答案,我有几点意见:
初始化/应用:同意它是反模式/气味。
Yacoub Massad:我同意IoC容器在原始依赖关系方面是个问题。如前所述,手动(穷人的)DI对于较小或架构稳定的系统来说听起来很棒,但我认为维护大量手动配置的合成根可能会变得非常困难
选项:
1) 工厂作为依赖项(当需要运行时解析时)
2) 如前所述,将有状态对象与纯服务分开
(1) :这就是我一直在做的事情,但我意识到有可能引发另一种反模式:服务定位器。
(2) :我对我的特殊情况的偏好是这一个关于这一个,因为我可以清楚地区分这两种类型。纯服务是一个简单易懂的IoC容器,而有状态对象解析将取决于它们是否具有原始依赖关系
每次我“不得不”使用依赖注入时,它都是以教条的方式使用的,通常是在主管的命令下使用的,主管执意不惜任何代价将DI应用到IoC容器中。您的示例中的
税率
就是一个例子。和其他依赖项一样,基本依赖项应该正常地注入构造函数中。以下是构造函数的外观:
public PackageProcessor(IProductProcessor productProcessor, double taxRate)
{
_productProcessor = productProcessor;
_taxRate = taxRate;
}
在我看来,DI容器不能很好/很容易地支持原语依赖性这一事实是DI容器的一个问题/弱点
在我看来,最好使用对象组合而不是DI容器。一个原因是它支持更容易地注入基元依赖项。另一个原因请参见
使用Initialize
方法存在一些问题。它需要调用Initialize
方法,从而使对象的构造更加复杂。此外,程序员可能忘记调用Initialize
方法,这会使对象处于无效状态。这也意味着本例中的taxRate
是一个隐藏的依赖项。程序员只需查看构造函数,就不会知道您的类依赖于这种原始依赖关系
Initialize
方法的另一个问题是,可能会使用不同的值调用它两次。另一方面,构造函数确保依赖项不会更改。您需要创建一个特殊的布尔变量(例如,isInitialized
),以检测Initialize
方法是否已被调用。这只会使事情复杂化。您的示例中的税率是一个很好的例子。和其他依赖项一样,基本依赖项应该正常地注入构造函数中。以下是构造函数的外观:
public PackageProcessor(IProductProcessor productProcessor, double taxRate)
{
_productProcessor = productProcessor;
_taxRate = taxRate;
}
在我看来,DI容器不能很好/很容易地支持原语依赖性这一事实是DI容器的一个问题/弱点
在我看来,最好使用对象组合而不是DI容器。一个原因是它支持更容易地注入基元依赖项。另一个原因请参见
使用Initialize
方法存在一些问题。它需要调用Initialize
方法,从而使对象的构造更加复杂。此外,程序员可能忘记调用Initialize
方法,这会使对象处于无效状态。这也意味着本例中的taxRate
是一个隐藏的依赖项。程序员只需查看构造函数,就不会知道您的类依赖于这种原始依赖关系
Initialize
方法的另一个问题是,可能会使用不同的值调用它两次。另一方面,构造函数确保依赖项不会更改。您需要创建一个特殊的布尔变量(例如,isInitialized
),以检测Initialize
方法是否已被调用。这只会使事情复杂化
我读了一些认为这两步初始化反模式
Initialize
方法导致。称它为一个可能太严格了,但它确实是一个
如何向组件提供此值取决于它是什么类型的值。有两种风格:配置值和运行时值:
- 配置值:如果它是一个常量/配置值,在组件的生命周期内不会改变,则该值应直接注入构造函数
- 运行时值:如果值在运行时发生变化(例如特定于请求的值),则不应在初始化期间提供该值(既不通过构造函数也不使用某些
初始化
方法)。使用运行时数据初始化组件
我部分同意@YacoubMassad关于使用DI容器配置基本依赖项的观点。容器提供的API不支持设置这些值