使用ThreadStatic而不是DI容器有什么问题

dan*_*dan 8 .net unit-testing dependency-injection

我正在尝试制作一个非常大,非常遗留的项目.

我们的大多数代码都使用了许多静态可用的服务.问题是这些很难嘲笑.他们曾经是单身人士.现在它们是伪单体 - 相同的静态接口,但是函数委托给可以切换的实例对象.像这样:

class ServiceEveryoneNeeds
{
    public static IImplementation _implementation = new RealImplementation();

    public IEnumerable<FooBar> GetAllTheThings() { return _implementation.GetAllTheThings(); }
}
Run Code Online (Sandbox Code Playgroud)

现在在我的单元测试中:

void MyTest()
{
    ServiceEveryoneNeeds._implementation = new MockImplementation();
}
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好.在prod中,我们只需要一个实现.但测试并行运行,可能需要不同的模拟,所以我这样做:

class Dependencies
{
     //set this in prod to the real impl
     public static IImplementation _realImplementation;

     //unit tests set these
     [ThreadStatic]
     public static IImplementation _mock;

     public static IImplementation TheImplementation
     { get {return _realImplementation ?? _mock; } }

     public static void Cleanup() { _mock = null; }
}
Run Code Online (Sandbox Code Playgroud)

然后:

class ServiceEveryoneNeeds
{
     static IImplementation GetImpl() { return Dependencies.TheImplementation; }

     public static IEnumerable<FooBar> GetAllTheThings() {return GetImpl().GetAllTheThings(); }

}

//and
void MyTest()
{
    Dependencies._mock = new BestMockEver();
    //test
    Dependencies.Cleanup();
}
Run Code Online (Sandbox Code Playgroud)

我们选择了这条路线,因为这是一个庞大的构建工程项目,将这些服务注入到需要它们的每个类中.同时,这些是我们的代码库中的大多数功能所依赖的通用服务.

我理解这种模式在隐藏依赖关系的意义上是不好的,而不是构造函数注入使得依赖关系显式化.

然而,好处是:
- 我们可以立即开始单元测试,而不是进行3个月的重构,然后进行单元测试.
- 我们仍然有全局变量,但这似乎比我们现在更好.

虽然我们的依赖关系仍然是隐含的,但我认为这种方法比我们的方法更好.除了隐藏的依赖关系,这在某种程度上比使用适当的DI容器更糟糕吗?我会遇到什么问题?

Seb*_*ber 4

它的服务定位器很糟糕。但你已经知道了。如果您的代码库如此庞大,为什么不开始部分迁移呢?向容器注册单例实例,并在您触摸代码中的类时启动构造函数注入它们。然后,您可以让大部分部件处于(希望如此)工作状态,并在其他地方获得 DI 的好处。

理想情况下,没有 DI 的零件应该随着时间的推移而收缩。您可以立即开始测试。

  • 不,这不是服务定位器。这是一个环境上下文。细微但重要的区别是类型被明确定义,而 ServiceLocator 被设计为提供任何类型。并且有一个本地默认值,因此代码将在没有任何配置的情况下以默认行为运行,但它仍然允许替换本地默认值以获得不同的行为。如果在正确的情况下使用,这还不错。虽然这种情况非常罕见。 (3认同)