C# 具有配置依赖关系的常用对象
我们的应用程序中有一个非常常见的对象。在这种情况下,我们称之为球。球工作正常,但在某些配置中,它们的行为不同。目前的设置如下:C# 具有配置依赖关系的常用对象,c#,design-patterns,configuration,dependency-injection,C#,Design Patterns,Configuration,Dependency Injection,我们的应用程序中有一个非常常见的对象。在这种情况下,我们称之为球。球工作正常,但在某些配置中,它们的行为不同。目前的设置如下: class Ball { private static readonly bool BallsCanExplode; static Ball() { bool.TryParse(ConfigurationManager.AppSettings["ballsCanExplode"], out BallsCa
class Ball
{
private static readonly bool BallsCanExplode;
static Ball()
{
bool.TryParse(ConfigurationManager.AppSettings["ballsCanExplode"],
out BallsCanExplode);
}
public Ball(){}
}
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;
}
}
这在实践中效果非常好。如果配置为球可以爆炸,则它们会爆炸,如果不爆炸,则不会爆炸。问题是它是完全不可测试的。我还没有找到一个好方法来保持它的可测试性,并且仍然易于实例化
最简单的解决方案是将球与配置解耦:
class Ball
{
private readonly bool CanExplode;
public Ball(bool canExplode);
}
这样做的问题是,Ball类中曾经是一个孤立的依赖项的内容现在扩展到了生成Ball的每个类。如果这种依赖性被注入,那么爆炸球的知识就必须被注入到任何地方
球厂也存在同样的问题。虽然每个类都可以使用newball()
,但它现在必须知道必须到处注入的BallFactory。另一个选项是使用已烘焙到应用程序中的服务定位器:
class Ball
{
private readonly bool CanExplode;
public Ball()
{
CanExplode = ServiceLocator.Get<IConfiguration>().Get("ballsCanExplode");
}
}
班级舞会
{
私有只读bool CanExplode;
公共舞会
{
CanExplode=ServiceLocator.Get().Get(“ballsCanExplode”);
}
}
这仍然将配置依赖性保留在球中,但允许注入测试配置。不过,Ball的使用太多,在每次newball()
调用中查找服务似乎有些过分
保持此可测试性以及易于实例化的最佳方法是什么
注意:应用程序中有一个依赖项注入框架和服务定位器,它们都经常使用。实例化balls的类应该作为依赖项接收一个
BallFactory
。无论是否生产爆炸球或非爆炸球,球厂
都可以相应地配置为应用程序启动
不要让BallFactory
读取应用程序配置文件以确定要生产的球的类型。应将其注入球厂
服务定位器是一种反模式。不要使用它们。我投票赞成配置服务路径。对于典型的服务定位器实现来说,开销不应该太高,如果以后需要,可以缓存配置服务。更好的是,使用依赖项注入框架,您不需要显式地定位服务。类似这样的情况如何:
class Ball
{
private static readonly bool BallsCanExplode;
static Ball()
{
bool.TryParse(ConfigurationManager.AppSettings["ballsCanExplode"],
out BallsCanExplode);
}
public Ball(){}
}
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;
}
}
通过这种方式,您可以使ball类内部构件对您的单元测试程序集可见,并注入一个自定义ballconfigurer。我将使用类似于ServiceLocator的东西来设置一个静态的DefaultBallsCanExplode,然后可能有一个重载构造函数,可以将ballsCanExplode bool作为一个选项
保持简单 正如我正确理解的,应用程序中的所有球的行为都是一样的。它们要么爆炸,要么不爆炸,由配置开关决定。您可以做的是在DI框架中配置它。根据框架的不同,应用程序根目录中的连接可能如下所示:
bool ballsCanExplode =
bool.Parse(ConfigurationManager.AppSettings["ballsCanExplode"]);
container.Register<Ball>(() => new Ball(ballsCanExplode));
bool-ballscan爆炸=
解析(ConfigurationManager.AppSettings[“ballsCanExplode”]);
容器。寄存器(()=>新球(ballsCanExplode));
执行此操作时,您可以使用服务定位器模式获取球的新实例,就像您已经使用的那样:
ServiceLocator.Get<Ball>();
ServiceLocator.Get();
但是最好让DI框架在其他类型的构造函数中注入Ball
依赖项(更容易测试)。像Ninject这样的D.I.库可以帮助您吗?您似乎知道依赖项注入选项,但不想遵循它们。因此,您最好的选择是添加一个用于测试的构造函数:如果您没有其他依赖项注入方法,那么publicball(boolcanexplode)服务定位器可以是一种干净的方法来完成这项工作。工厂在许多情况下都可能是杀伤力过大的,尽管它在这种特殊情况下可能是合适的(在这种情况下,您需要创建同一类的多个实例,并在每种情况下解析一个设置)。服务定位器已经在应用程序中无处不在,因此它已经存在,无论它是否是反模式。有些东西使用它,有些东西使用依赖项注入。@Snea:嗯,我认为你应该停止在服务定位器上添加更多依赖项。这基本上是我尝试的第一件事,但使用内置BallConfigurer似乎无法实现将Ball与AppSettings类解耦的目的。嗯,您说过希望球易于实例化,但希望它可测试。这给了你一切。您从未说过ball与AppSetting类的耦合过于紧密,您只是希望它足够解耦以进行单元测试。这使您知道,+1配置是应用程序初始化的一部分,而不是其运行时。