C# 依赖注入副作用(两步初始化反模式)?

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

我正在从事一个项目,其中我的构造函数只包含行为依赖项。i、 e.我从不传递值/状态

例如:

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不支持设置这些值