【问题标题】:Commonly used object with configuration dependency具有配置依赖的常用对象
【发布时间】:2010-11-03 15:29:47
【问题描述】:

我们的应用程序中有一个非常常见的对象。在这种情况下,我们将其称为 Ball。球工作正常,但在某些配置中它们的作用不同。目前是这样设置的:

class Ball
{
    private static readonly bool BallsCanExplode;
    static Ball()
    {
        bool.TryParse(ConfigurationManager.AppSettings["ballsCanExplode"], 
            out BallsCanExplode);
    }
    public Ball(){}
}

这在实践中完全没问题。如果配置是球可以爆炸,它们会爆炸,如果不是,则不会。问题是它是完全不可测试的。我还没有找到一种保持它可测试并且仍然易于实例化的好方法。

最简单的解决方案是将球和配置解耦:

class Ball
{
    private readonly bool CanExplode;
    public Ball(bool canExplode);
}

这样做的问题是,曾经在 Ball 类中孤立的依赖关系现在已经蔓延到每个生成 Ball 的类中。如果这被注入了依赖项,那么爆炸球的知识必须到处注入。

BallFactory 也存在同样的问题。虽然每个类都可以转到new Ball(),但它现在必须知道必须在任何地方注入的 BallFactory。另一种选择是使用已经嵌入应用程序的服务定位器:

class Ball
{
    private readonly bool CanExplode;
    public Ball()
    {
        CanExplode = ServiceLocator.Get<IConfiguration>().Get("ballsCanExplode");
    }
}

这仍然保留了球中的配置依赖关系,但允许注入测试配置。尽管球被大量使用,以至于在每次new Ball() 调用时定位服务似乎有点过头了。

保持这种可测试性以及易于实例化的最佳方式是什么?

注意:应用中既有依赖注入框架,也有服务定位器,都是经常用到的。

【问题讨论】:

  • 那些 D.I. Ninject 之类的库有帮助吗?
  • 您似乎知道依赖注入选项,但不想遵循它们。因此,您最好的选择是添加一个构造函数进行测试: public Ball(bool CanExplode)

标签: c# design-patterns configuration dependency-injection


【解决方案1】:

实例化球的类应该接收BallFactory 作为依赖项。 BallFactory 可以相应地配置为应用程序启动是否产生爆炸球或非爆炸球。

不要让BallFactory 读取应用程序配置文件来确定要生产哪种类型的球。那应该注入BallFactory

服务定位器是一种反模式。不要使用它们。

【讨论】:

  • 如果您没有其他依赖注入方法,服务定位器可以是一种干净的方式来完成此任务。在许多情况下,工厂可能会过度杀伤,尽管在这种特殊情况下可能是合适的(您需要创建同一类的许多实例并在每种情况下解析一个设置。)
  • 服务定位器在应用程序中已经无处不在,所以无论它是否是反模式,它都已经存在。有些东西使用它,有些东西使用依赖注入。
  • @Snea:好吧,我认为你应该停止在服务定位器上添加更多依赖项。
【解决方案2】:

我会使用 something 像您的 ServiceLocator 来设置 static DefaultBallsCanExplode,然后可能有一个可以将 ballsCanExplode bool 作为选项的重载构造函数。

保持简单!

【讨论】:

    【解决方案3】:

    据我了解,您的应用程序中的所有球始终表现相同。它们要么爆炸,要么不爆炸,由配置开关决定。您可以做的是在您的 DI 框架中配置它。根据框架,应用程序根目录中的连接可能如下所示::

    bool ballsCanExplode =
        bool.Parse(ConfigurationManager.AppSettings["ballsCanExplode"]);
    
    container.Register<Ball>(() => new Ball(ballsCanExplode)); 
    

    当你这样做时,你可以使用服务定位器模式来获取一个球的新实例,就像你已经习惯的那样:

    ServiceLocator.Get<Ball>();
    

    但更好的是让 DI 框架在其他类型的构造函数中注入 Ball 依赖项(更容易测试)。

    【讨论】:

    • +1 配置是应用程序初始化的一部分,而不是其运行时。
    【解决方案4】:

    我投票支持配置服务路径。对于典型的服务定位器实现,开销应该不会很高,如果以后需要,可以缓存配置服务。更好的是,使用依赖注入框架,您无需显式定位服务。

    【讨论】:

      【解决方案5】:

      这样的事情怎么样:

      internal interface IBallConfigurer
      {
          bool CanExplode { get; }
      }
      
      internal class BallConfigurer : IBallConfigurer
      {
          public bool CanExplode
          {
              get
              {
                  bool BallsCanExplode;
                  bool.TryParse(ConfigurationManager.AppSettings["ballsCanExplode"],
              out BallsCanExplode);
                  return BallsCanExplode;
      
              }
          }
      }
      
      public class Ball
      {
          private bool canExplode;
      
          public Ball()
              :this(new BallConfigurer())
          {
      
          }
      
          internal Ball(IBallConfigurer ballConfigurer)
          {
              this.canExplode = ballConfigurer.CanExplode;
          }
      }
      

      这样,您可以使球类内部对您的单元测试程序集可见,并注入自定义球配置器。

      【讨论】:

      • 这基本上是我尝试的第一件事,但是内置 BallConfigurer 似乎违背了将 Ball 与 AppSettings 类解耦的目的。
      • 嗯,您说您希望球易于实例化,但您希望它可测试。这给了你这一切。您从未说过球与 AppSetting 类的耦合过于紧密,您只是希望它足够解耦以进行单元测试。这给了你。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-12-31
      • 1970-01-01
      • 2021-04-25
      • 1970-01-01
      • 1970-01-01
      • 2012-09-28
      • 1970-01-01
      相关资源
      最近更新 更多