扩展方法是否隐藏依赖关系?

时间:2009-08-18 22:36:48

标签: c#-3.0 inversion-of-control extension-methods dependencies testability

所有

想要对此有所了解。最近,我在设计/开发时越来越多地成为“纯粹的”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 个答案:

答案 0 :(得分:18)

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

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

换句话说:如果TruncateToSizeString的真正成员,您是否会考虑使用它?你是否试图模拟整数运算,引入IInt32Adder等?当然不是。这是一样的,只是你碰巧提供了实现。单元测试TruncateToSize,并且不用担心。

答案 1 :(得分:16)

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

测试TruncateToLength不会涉及嘲笑它,它只需要创建一些字符串并测试该方法实际返回了正确的值。

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

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

答案 2 :(得分:0)

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

  1. 是的,废弃扩展它的PITA来模拟
  2. 使用扩展程序并测试它做了正确的事情。即将数据传递到truncate并检查它是否被截断
  3. 如果这不是一件小事,我必须嘲笑它​​,我会让我的扩展类为它使用的服务设置一个setter,并在测试代码中设置它。
  4. 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做一些聪明的事情模式,以避免生产中使用的二传手。

答案 3 :(得分:0)

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

我会看看动态语言,看看他们如何在日常工作中应对这些问题,这真的很有启发性。特别是当他们除了良好的设计和纪律之外没有什么能阻止他们做愚蠢的事情。在运行时一切都是动态的,唯一阻止它们的是计算机抛出一个主要的运行时错误。 “Duck Typing”是我见过的最疯狂的事情,良好的代码归功于优秀的程序设计,尊重团队中的其他人,以及每个成员的信任,尽管有能力做一些古怪的事情选择不要因为好设计带来更好的结果。

对于使用模拟对象/ ICO / DI的测试场景,您是否真的会在扩展方法中放置一些重要的工作,或者只是以功能类型的方式操作一些简单的静态内容?我倾向于像在功能编程风格中那样使用它们,输入输入,结果在中间没有魔法,只是直接的框架类,你知道MS的人已经设计和测试:你可以依赖的P上。

如果您正在使用扩展方法做一些繁重的工作,我会再次查看您的程序设计,查看您的CRC设计,类模型,用例,DFD,动作图或您想要使用的任何内容,并找出其中的位置这个设计你打算把这些东西放在扩展方法中,而不是适当的类。

在一天结束时,您只能对您的系统设计进行测试,而不能在您的范围之外进行测试。如果您要使用扩展类,我的建议是查看对象组合模型,并仅在有充分理由时才使用继承。

对象组合总是胜出我,因为它们产生可靠的代码。您可以将它们插入,取出它们并随意使用它们。请注意,这一切都取决于您是否使用接口作为设计的一部分。此外,如果使用Composition类,则类层次结构树将展平为离散类,并且通过继承类获取扩展方法的位置较少。

如果你必须像扩展方法一样使用一个作用于另一个类的类,请首先查看访问者模式,然后确定它是否是更好的路径。