具有配置依赖的常用对象

发布于 2024-09-30 14:55:56 字数 1104 浏览 3 评论 0原文

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

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() 调用时找到该服务。

保持其可测试性并且易于实例化的最佳方法是什么?

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

We have a quite common object in our application. In this case, we'll call it a Ball. Balls work fine, but in some configurations they act differently. It is currently set up like this:

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

This works completely fine in practice. If the configuration is that balls can explode, they explode, and if not, not. The problem is that it is completely non-testable. I haven't been able to figure out a good way to keep it testable, and still easy to instantiate.

The simplest solution is to just decouple the ball and the configuration:

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

The problem with this is that what was once an isolated dependency in the Ball class has now spread to every single class that makes a Ball. If this gets dependency injected in, then the knowledge of exploding balls has to get injected everywhere.

The same problem exists with a BallFactory. While every class could just go new Ball(), it now has to know about a BallFactory that has to be injected everywhere. The other option is to use the Service Locator which is already baked in to the application:

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

This still keeps the configuration dependency in the ball, but allows a test configuration to be injected in. Balls are used so much though, that it seems like overkill to locate the service on every new Ball() call.

What would be the best way to keep this testable, as well as easy to instantiate?

Note: There is both a dependency injection framework and service locator in the application, which are both used frequently.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

无戏配角 2024-10-07 14:55:56

实例化球的类应该接收 BallFactory 作为依赖项。 BallFactory 可以在应用程序启动时进行相应配置,无论是生成爆炸球还是非爆炸球。

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

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

Classes that instantiate balls should receive a BallFactory as a dependency. The BallFactory can be configured accordingly as application startup whether or not to produce exploding balls or non-exploding balls.

Don't have the BallFactory read the application configuration file to determine which types of balls to produce. That should be injected into the BallFactory.

Service locators are an anti-pattern. Don't use them.

苍白女子 2024-10-07 14:55:56

我会使用诸如 ServiceLocator 之类的东西来设置 静态 DefaultBallsCanExplode,然后可能有一个重载的构造函数,可以将 ballsCanExplode bool 作为选项。

保持简单!

I would use something like your ServiceLocator to set a static DefaultBallsCanExplode, then perhaps have an overloaded constructor that can take a ballsCanExplode bool as an option.

Keep it simple!

看海 2024-10-07 14:55:56

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

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

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

当您执行此操作时,您可以使用服务定位器模式来获取球的新实例,就像您已经习惯的那样:

ServiceLocator.Get<Ball>();

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

As I understand correctly, all balls in your application always act the same. Either they explode or they don't, determined by a configuration switch. What you can do is configure this in your DI framework. Depending on the framework the wiring in the application root could look like this::

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

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

When you do this you can use the Service Locator pattern to fetch a new instance of a ball as you are already used to do:

ServiceLocator.Get<Ball>();

But better would be to let the DI framework inject Ball dependencies in the constructor of some other type (much easier for testing).

一紙繁鸢 2024-10-07 14:55:56

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

I vote for the configuration service path. The overhead should not be very high for a typical service locator implementation and you can cache the configuration service if you need it later. Better yet, use a dependency-injection framework and you won't need to explicitly locate the service.

对你的占有欲 2024-10-07 14:55:56

怎么样:

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

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

How about something like this:

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

This way, you can make the ball class internals visible to your unit test assembly, and inject a custom ballconfigurer.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文