15

全部,

想对此有一些想法。最近,在设计/开发时,我越来越成为“纯粹” DI/IOC 原则的订户。其中一部分(很大一部分)涉及确保我的类之间几乎没有耦合,并且它们的依赖关系通过构造函数解决(当然还有其他管理方法,但你明白了)。

我的基本前提是扩展方法违反了 DI/IOC 的原则。

我创建了以下扩展方法,用于确保插入到数据库表中的字符串被截断为正确的大小:

public static class StringExtensions
{
    public static string TruncateToSize(this string input, int maxLength)
    {
        int lengthToUse = maxLength;
        if (input.Length < maxLength)
        {
            lengthToUse = input.Length;
        }

        return input.Substring(0, lengthToUse);
    }
}

然后我可以从另一个类中调用我的字符串,如下所示:

string myString = "myValue.TruncateThisPartPlease.";
myString.TruncateToSize(8);

不使用扩展方法的公平翻译是:

string myString = "myValue.TruncateThisPartPlease.";
StaticStringUtil.TruncateToSize(myString, 8);

使用上述任一示例的任何类都不能独立于包含 TruncateToSize 方法(除了 TypeMock)的类进行测试。如果我没有使用扩展方法,并且我不想创建静态依赖项,它看起来更像:

string myString = "myValue.TruncateThisPartPlease.";
_stringUtil.TruncateToSize(myString, 8);

在最后一个示例中,_stringUtil 依赖项将通过构造函数解析,并且可以在不依赖实际 TruncateToSize 方法的类的情况下测试该类(它可以很容易地模拟)。

从我的角度来看,前两个示例依赖于静态依赖(一个显式,一个隐藏),而第二个示例反转依赖并提供减少的耦合和更好的可测试性。

那么扩展方法的使用与DI/IOC原则有冲突吗?如果您是 IOC 方法的订阅者,您会避免使用扩展方法吗?

4

4 回答 4

18

我认为这很好——因为它不像TruncateToSize是一个现实可替换的组件。这是一种只需要做一件事的方法。

您不需要能够模拟所有内容- 只需破坏单元测试(文件访问等)或您想要根据真正依赖项进行测试的服务。如果您使用它来执行身份验证或类似的事情,那将是一个非常不同的事情......但只是做一个完全没有可配置性的直接字符串操作,不同的实现选项等 - 将其视为依赖项是没有意义的在正常意义上。

换句话说:如果TruncateToSize是真正的会员String,你会三思而后行吗?您是否也尝试模拟整数算术,引入IInt32Adder等?当然不是。这是一样的,只是你碰巧提供了实现。进行单元测试,TruncateToSize不用担心。

于 2009-08-18T22:45:34.347 回答
17

我知道您来自哪里,但是,如果您试图模拟扩展方法的功能,我相信您使用它们不正确。应该使用扩展方法来执行如果没有它们在语法上会很不方便的任务。您的 TruncateToLength 就是一个很好的例子。

测试 TruncateToLength 不会涉及模拟它,它只涉及创建一些字符串并测试该方法是否实际返回了正确的值。

另一方面,如果您的数据层中有代码包含在访问数据存储的扩展方法中,那么是的,您遇到了问题,测试将成为问题。

我通常只使用扩展方法来为小的、简单的操作提供语法糖。

于 2009-08-18T22:42:57.770 回答
1

扩展方法、部分类和动态对象。我真的很喜欢他们,但是你必须小心,这里有怪物。

我会看一下动态语言,看看它们是如何每天处理这些问题的,这真的很有启发性。尤其是当他们除了良好的设计和纪律之外没有什么可以阻止他们做愚蠢的事情时。一切在运行时都是动态的,唯一阻止它们的是计算机抛出一个重大的运行时错误。“Duck Typing”是我见过的最疯狂的事情,好的代码取决于好的程序设计,尊重团队中的其他人,以及对每个成员的信任,虽然有能力做一些古怪的事情,但选择不因为好设计会带来更好的结果。

As for your test scenario with mock objects/ICO/DI, would you really put some heavy duty work in an extension method or just some simple static stuff that operate in a functional type way? I tend to use them like you would in a functional programming style, input goes in, results come out with no magic in the middle, just straight up framework classes that you know the guys at MS have designed and tested :P that you can rely on.

If your are doing some heavy lifting stuff using extension methods I would look at your program design again, check out your CRC designs, Class models, Use Cases, DFD's, action diagrams or whatever you like to use and figure out where in this design you planned to put this stuff in an extension method instead of a proper class.

At the end of the day, you can only test against your system design and not code outside of your scope. If you going to use extension classes, my advice would be to look at Object Composition models instead and use inheritance only when there is a very good reason.

Object Composition always wins out with me as they produce solid code. You can plug them in, take them out and do what you like with them. Mind you this all depends on whether you use Interfaces or not as part of your design. Also if you use Composition classes, the class hierarchy tree gets flattened into discrete classes and there are fewer places where your extension method will be picked up through inherited classes.

If you must use a class that acts upon another class as is the case with extension methods, look at the visitor pattern first and decide if its a better route.

于 2011-08-04T02:31:05.143 回答
0

这是一种痛苦,因为它们很难被嘲笑。我通常使用其中一种策略

  1. 是的,取消扩展它的一个 PITA 来模拟
  2. 使用扩展并测试它是否做对了。即将数据传递到截断并检查它是否被截断
  3. 如果这不是一件小事,我必须模拟它,我会让我的扩展类有一个它使用的服务的设置器,并在测试代码中设置它。

IE

static class TruncateExtensions{

    public ITruncateService Service {private get;set;}
    public string TruncateToSize(string s, int size)
    {
         return (Service ?? Service = new MyDefaultTranslationServiceImpl()). TruncateToSize(s, size);
    }
}

这有点吓人,因为有人可能会在不应该设置服务的时候设置服务,但有时我有点傲慢,如果它真的很重要,我可以使用 #if TEST 标志或 ServiceLocator 模式做一些聪明的事情来避免在生产中使用的setter。

于 2009-08-18T22:53:27.610 回答