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

时间:2015-11-21 00:00:43

标签: c# dependency-injection dependencies

我正在开发一个项目,其中我的构造函数只包含 - 行为依赖项。即我从未传递过值/状态。

示例:

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

}

为了通过状态,决定包括第二步(调用Initialize)。

我知道我们可以将它配置为IoC Container配置类中的命名参数,但是,我们不喜欢在配置文件中创建“new namedParameter(paramvalue)”的想法,因为它使它不必要地不可读创造了未来的维持痛点。

我在不止一个地方看过这种模式。

问题:我读过一些考虑这两步初始化的反模式。如果这是共识,那么这不意味着通过IoC容器的依赖注入方法中的各种限制/弱点吗?

修改: 在研究了Mark Seeman的suggestion

之后

以及这个问题的答案,我有几点意见: 初始化/应用:同意它是反模式/气味。 Yacoub Massad:我同意IoC容器在原始依赖关系方面是一个问题。手动(穷人)DI,正如所描述的那样here对于较小或体系结构稳定的系统来说听起来很棒,但我认为维护一些手动配置的组合根很难。

选项: 1)工厂作为依赖关系(当需要运行时解析时) 2)如描述here所述,将有状态对象与纯服务分开。

(1):这就是我一直在做的事情,但我意识到有可能引发另一种反模式:服务定位器。 (2):我对我的特殊情况的偏好是关于这一个的,因为我可以干净地分离这两种类型。纯粹的服务是没有道理的 - IoC容器,而有状态的对象解析将取决于它们是否具有原始依赖性。

每当我'不得不'使用依赖注入时,它一直以一种教条的方式使用,通常是在主管的命令下,他们不惜一切代价向IoC容器应用DI。

2 个答案:

答案 0 :(得分:5)

  

我读过一些考虑这两步初始化的反模式

Initialize方法会导致Temporal Coupling。将其称为anti-pattern可能过于严格,但肯定是Design Smell

如何将此值提供给组件取决于它的值类型。有两种形式:配置值和运行时值:

  • 配置值:如果它是一个在组件生命周期内不会改变的常量/配置值,则该值应直接注入构造函数。

  • 运行时值:如果值在运行时期间发生变化(例如请求特定值),则在初始化期间应该提供值(不能通过构造函数也不使用某些Initialize方法)。使用运行时数据初始化组件IS an anti-pattern

我部分同意@YacoubMassad关于使用DI容器配置原始依赖关系的问题。容器提供的API在使用自动布线时无法以可维护的方式设置这些值。我认为这主要是由于C#和.NET的限制造成的。在设计和开发Simple Injector时我在使用这样的API时遇到了很长时间,但是我决定完全省略这样的API,因为我没有找到一种方法来定义既直观又容易导致代码的API对用户可维护。因此,我通常建议开发人员将原始类型提取到参数对象中,而是将参数对象注册并注入到消费类型中。换句话说,TaxRate属性可以包含在ProductServiceSettings类中,并且此参数对象可以注入ProductProcessor

但正如我所说,我只是部分赞同Yacoub。虽然手工制作一些物品更为实际(a.k.a. Pure DI),但他暗示这意味着你应该完全放弃DI容器。国际海事组织过于强烈。在我编写的大多数应用程序中,我使用容器批量注册了大约98%的类型,并且我将其他两个手动连接2%,因为自动连接它们太复杂了。这样可以在我的应用程序环境中获得最佳整体效果。当然,你的里程可能会有所不同。并非每个应用程序都真正受益于使用DI容器,并且我在我编写的所有应用程序中都没有自己使用容器。但是,我总是采用依赖注入模式和SOLID原则。

答案 1 :(得分:3)

示例中的taxRatePrimitive Dependency。原始依赖项应该在构造函数中正常注入,就像其他依赖项一样。以下是构造函数的外观:

public PackageProcessor(IProductProcessor productProcessor, double taxRate)
{
    _productProcessor = productProcessor;
    _taxRate = taxRate;
}

在我看来,DI容器不能很好地/轻易地支持原始依赖性这一事实是DI容器的问题/弱点。

在我看来,最好将Pure DI用于对象组合而不是DI容器。一个原因是它支持更容易注入原始依赖项。另请参阅this article

使用Initialize方法存在一些问题。它通过要求调用Initialize方法使对象的构造更复杂。此外,程序员可能忘记调用Initialize方法,这会使您的对象处于无效状态。这也意味着此示例中的taxRate是隐藏的依赖项。程序员不会通过简单地查看构造函数来知道你的类依赖于这种原始依赖。

Initialize方法的另一个问题是它可能会使用不同的值调用两次。另一方面,构造函数确保依赖关系不会发生变化。您需要创建一个特殊的布尔变量(例如isInitialized)来检测是否已经调用了Initialize方法。这只会让事情变得复杂。