English 中文(简体)
使用线索静态而不是 DI 容器有什么错
原标题:What s wrong with using ThreadStatic instead of a DI container

我想让一个非常大的,非常遗留的项目 能够测试。

我们有许多静态的可用服务, 我们的代码大多使用。 问题在于这些是很难嘲笑的。 它们过去是单质的。 现在它们是假单质的, 相同的静态界面, 但功能代表到一个可以被切换的实例对象。 例如 :

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

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

现在在我的单位测试中:

void MyTest()
{
    ServiceEveryoneNeeds._implementation = new MockImplementation();
}

目前为止,很好。在测试中,我们只需要一个执行。但测试平行进行,可能需要不同的模型,所以我做了:

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

然后:

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

我们选择了这条路线,因为它是一个庞大的工程,将这些服务注入每一个需要的阶层。 与此同时,这些是大多数功能都依赖的我们代码库中的普遍服务。

我理解,这种模式是坏的,因为它掩盖了依赖性,而不是建筑师注入,而建筑师注入明确表明依赖性。

However the benefits are:
- we can start unit testing immediately, vs doing a 3 month refactor and then unit testing.
- we still have globals, but this appears to be strictly better than where we were.

虽然我们的依存关系仍然隐含着,但我想说,这一办法绝对比我们的做法好。 除了隐藏的依附关系之外,这是否比使用适当的DI集装箱还要糟糕? 我将遇到什么问题?

最佳回答

http://blog.ploeh.dk/2010/02/03/ServiceLocatorIsAntiPatter.aspx" rel=“nofollow” 服务定位器,这是坏的 。 但是您已经知道这一点。 如果您的代码基础如此庞大, 为什么不开始部分迁移? 用容器登记单吨案例, 并在您触动代码中的某一类时开始输入它们。 然后您可以将大部分部件留在工作状态中, 并在其他地方获得DI的好处 。

理想的情况是,没有DI的部件会随着时间的流逝而缩小。你可以马上开始测试。

问题回答

这被称为",http://blogs.msdn.com/b/ploeh/archive/2007/07/23/environmentcontext.aspx" rel=“nofollow” > 环境环境上下文 。如果使用和执行正确,使用环境上下文没有错。在使用环境上下文时有一些先决条件:

  1. It must be a cross cutting concern that returns some value
  2. You need a local default
  3. You have to make sure that null can not be assigned. (Use a Null implementation instead)

对于不返回数值的交叉关切,例如伐木,你应该更喜欢拦截。 对于其他没有交叉关切的相互依存关系,你应该做建筑师注射。

您的执行存在一些问题( 并不妨碍指定无效、 命名、 默认) 。 以下是您如何执行 :

public class SomeCrossCuttingConcern
{
     private static ISomeCrossCuttingConcern default = new DefaultSomeCrossCuttingConcern();

     [ThreadStatic]
     private static ISomeCrossCuttingConcern current;

     public static ISomeCrossCuttingConcern Default
     { 
         get { return default; }
         set 
         { 
             if (value == null) 
                 throw new ArgumentNullException(); 
             default = value; 
         } 
     }

     public static ISomeCrossCuttingConcern Current
     { 
         get 
         { 
             if (current == null)
                 current = default; 
             return current; 
         }

         set 
         { 
             if (value == null) 
                 throw new ArgumentNullException(); 
             current = value; 
         } 
     }

     public static void ResetToDefault() { current = null; }
}

环境环境的优势在于,您不会因为交叉关注而污染自己的API。

但另一方面,关于测试,测试可能会变得依赖。例如,如果你忘记为一次测试设置模型,如果模拟是以前由另一次测试设置的,测试就会正确运行。但是,当它独立运行或以不同顺序运行时,测试会失败。它会让测试更加困难。

我认为你正在做的事情并不坏。 您正在试图使您的代码基数可以测试, 关键在于用小步进行测试。 您在阅读 < a href=> https://rads. stackoverflow.com/ amzn/ kick/ com/0131177052\\\ rel= “ 不跟随 norefererr” 时会得到同样的忠告。 但是, 您正在做的下边是, 一旦您开始使用快速注射, 您将不得不重新定义您的代码基数 。 但更重要的是, 您将不得不更改许多测试代码 。

我同意亚历克斯的观点。 最好使用制造器注射, 而不是使用环境环境。 您不必为此直接重构整个代码基数, 但是制造器注射会让呼叫堆积充气, 您需要做一个切口来防止它冒泡, 因为这迫使您在整个代码基数中做出许多改变 。

目前我在一个遗留的代码基数上工作, 无法在某些类型上使用 DI 容器( 疼痛 ) 。 我仍然在可以的地方使用构建器注射, 这有时意味着我必须重新使用 < a href=" http://lostechies.com/chadmyers/2009/07/14/ just- say- no- to-poor- man- s- defaitity- inpition/ " rel=" nofollown noreferr" > 贫穷的男性依赖性注射 < / a > 。 这是我用来阻止构建器注入泡沫的伎俩。 不过, 这比使用环境环境环境环境环境环境要好得多。 可怜的人 SDI 是次优化的, 但仍允许您写适当的单位测试, 并让您稍晚一些时候更容易打破默认的构建器 。

依赖性注射和使用DI容器实际上是独立的,尽管这自然导致另一个。 使用DI容器意味着代码有一定的结构。 这种结构可能更容易阅读,而且当然在不深入了解隐藏的依赖性的情况下更容易工作,因此更容易维持。

既然你不再依赖自律,你已经实施了一种控制反转的形式。我认为这是一种更好的设计,并且代表了使代码更容易测试的良好起点。听起来你从这一步得到了一些直接的价值。

您是否比隐含的依附关系( 换句话说, DI vs environmental condition) 更能有明确的依附关系? 我倾向于说是的,但它真的取决于成本和收益。 其好处取决于引入错误的成本、您在代码中可能看到多大的烦恼、调试有多难、谁将维持它、它的预期寿命是多少等等。

全球变异的静态状态总是坏的。 一些聪明的人可能会决定他们需要转换全球服务的实施,而他们正在打电话,然后换掉。 如果他们事后不清理,这可能会大错特错。 这可能是个愚蠢的例子,但这种意想不到的副作用总是坏的,因此最好通过设计来完全消除这些副作用。 你可以以纪律和警惕来防止它们,但难度更大。





相关问题
Manually implementing high performance algorithms in .NET

As a learning experience I recently tried implementing Quicksort with 3 way partitioning in C#. Apart from needing to add an extra range check on the left/right variables before the recursive call, ...

Anyone feel like passing it forward?

I m the only developer in my company, and am getting along well as an autodidact, but I know I m missing out on the education one gets from working with and having code reviewed by more senior devs. ...

How do I compare two decimals to 10 decimal places?

I m using decimal type (.net), and I want to see if two numbers are equal. But I only want to be accurate to 10 decimal places. For example take these three numbers. I want them all to be equal. 0....

Exception practices when creating a SynchronizationContext?

I m creating an STA version of the SynchronizationContext for use in Windows Workflow 4.0. I m wondering what to do about exceptions when Post-ing callbacks. The SynchronizationContext can be used ...

Show running instance in single instance application

I am building an application with C#. I managed to turn this into a single instance application by checking if the same process is already running. Process[] pname = Process.GetProcessesByName("...

How to combine DataTrigger and EventTrigger?

NOTE I have asked the related question (with an accepted answer): How to combine DataTrigger and Trigger? I think I need to combine an EventTrigger and a DataTrigger to achieve what I m after: when ...

热门标签