2010-11-03 58 views
2

我们的应用程序中有一个相当普遍的对象。在这种情况下,我们称之为球。球运行良好,但在一些配置中,它们的作用不同。它目前的设置是这样的:具有配置依赖性的常用对象

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类孤立的依赖,现在已经蔓延到每一个班级,使一个球。如果这种依赖被注入,那么爆炸球的知识必须注入到处。

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

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

这仍保持在球的配置依赖,但允许测试配置在被注入球用这么多。尽管如此,在每个new Ball()调用中找到该服务似乎有点矫枉过正。

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

注意:应用程序中同时存在依赖注入框架和服务定位器,这两者都经常使用。

+0

那些D.I.像Ninject这样的库有帮助吗? – xandy 2010-11-03 15:35:08

+0

您似乎意识到依赖注入选项,但不想遵循它们。因此,你最好的选择是添加一个构造函数进行测试:public Ball(bool CanExplode) – 2010-11-03 21:30:19

回答

5

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

没有BallFactory读取应用程序配置文件来确定生成哪种类型的球。应该注入BallFactory

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

+0

如果你没有其他的依赖注入方法,服务定位器可以成为一种干净的方式来实现这一点。在许多情况下,工厂可能会被矫枉过正,但在这种特殊情况下(您需要创建多个相同类的实例并解决每种情况下的设置)可能适用。 – 2010-11-03 15:37:50

+0

服务定位器已在应用程序中随处可见,所以它已经存在了,无论它是否是反模式。有些东西使用它,有些东西使用依赖注入。 – Snea 2010-11-03 15:38:32

+2

@Snea:嗯,我认为你应该停止在服务定位器上添加更多的依赖关系。 – jason 2010-11-03 15:54:09

0

我投票支持配置服务路径。典型的服务定位器实现的开销不应该太高,如果以后需要,可以缓存配置服务。更好的是,使用依赖注入框架,你不需要明确定位服务。

0

怎么是这样的:

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。

+0

这实质上是我尝试的第一件事,但是使用烘焙的BallConfigurer似乎打破了将球从AppSettings类中解耦的目的。 – Snea 2010-11-03 15:42:39

+0

那么,你说你想让球容易实例化,但你想要它可测试。这给你所有的。你从来没有说任何关于球与AppSetting类紧密结合的东西,你只是希望它足够脱离单元测试。这给你。 – BFree 2010-11-03 15:46:08

2

我会用东西喜欢你的服务定位器设置静态 DefaultBallsCanExplode,那么也许有一个重载的构造函数,可以采取一个ballsCanExplode布尔作为一个选项。

保持简单!

2

据我所知,你的应用程序中的所有球总是相同的。它们或者爆炸或者它们不是,由配置开关确定。你可以做的是在你的DI框架中进行配置。根据架构在应用程序根接线可能看起来像这样::

bool ballsCanExplode = 
    bool.Parse(ConfigurationManager.AppSettings["ballsCanExplode"]); 

container.Register<Ball>(() => new Ball(ballsCanExplode)); 

当你这样做,你可以使用Service Locator模式来获取一球的新实例,因为你已经习惯做的事:

ServiceLocator.Get<Ball>(); 

但更好的方式是让DI框架在一些其他类型的(多用于测试更容易)的构造函数注入Ball依赖。

+1

+1配置是应用程序初始化的一部分,而不是其运行时。 – 2010-11-03 23:17:39