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方法论的订户,您是否避免使用扩展方法?

+0

您的扩展方法不检查空输入! – canon 2011-08-04 02:39:04

+0

@antisanity:AFAIK对于扩展方法的实例参数(在本例中为* input *)为空,在逻辑上是不可能的。 – 2011-08-04 03:24:23

+3

是的,菲尔......它可以是空的。 – canon 2011-08-22 14:10:33

回答

15

我看到你在哪里但是来了,如果你正在尝试模拟出一个扩展方法的功能,我相信你正确使用它们。应该使用扩展方法来执行任务,如果没有这些任务,语法上会很不方便。你的TruncateToLength就是一个很好的例子。

测试TruncateToLength不会涉及嘲笑它,它只会涉及弦数的创建和测试方法实际返回正确的值。

在另一方面,如果你在包含在被访问您的数据存储扩展方法,你的数据层有代码,那么,你有问题,检测将成为一个问题。

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

18

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

你不需要能够嘲笑的一切 - 只是服务,要么破坏单元测试(文件访问等),或者你想测试真正的依赖关系方面的服务。如果您使用它来执行身份验证或类似的事情,这将是一个非常不同的问题......但只是做一个直接的字符串操作,它绝对没有可配置性,不同的实现选项等 - 没有意义将其视为依赖项在正常意义上说。

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

+0

与我的帖子一样的想法,同意 – LorenVS 2009-08-18 22:46:51

+0

所以,我不反对你的回答 - 这是非常实用的。你绝对可以把国际奥委会(或任何设计理论)拿走太远。 为了争辩:假设你致力于国际石油公司,你会没有问题的第二个实施例(对静态类的显式依赖)?我从来没有读过IOC的铁杆倡导者的文章,称静态依赖是可以接受的。 – 2009-08-19 14:02:58

+0

如果有两种TruncateToSize算法会怎样?一种为内存交易速度,另一种为速度交易内存。如果它有一个界面,在这种情况下它不会更好吗?既然你无法预测这些需求,你不应该简单地把它作为一个接口吗? – 2015-01-29 18:45:40

0

这是一个痛苦,因为他们很难嘲笑。我通常使用这些策略

  1. 没错之一,报废扩展它的皮塔模拟出
  2. 使用扩展,只是测试它做了正确的事情。即传递数据到截断并检查它被截断如果它不是一些微不足道的事情,我必须嘲笑它​​,我会让我的扩展类为它使用的服务设置一个setter,并在测试中设置它码。

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被用于生产。

0

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

我会看看动态语言,看看他们如何应对这些日常的问题,这真的很有启发性。特别是当他们没有任何阻止他们做出愚蠢的事情,除了良好的设计和纪律。在运行时,一切都是动态的,唯一阻止它们的就是计算机抛出重大运行时错误。 “鸭子打字”是我见过的最疯狂的事情,好的代码是好的程序设计,尊重团队中的其他人,并且相信每个成员,尽管有能力做一些古怪的事情,选择不是因为好设计导致更好的结果。

至于你的模拟对象/ ICO/DI的测试场景,你真的会把一些繁重的工作放在扩展方法中,或者只是一些简单的静态的东西,以功能类型的方式运行?我倾向于像使用函数式编程风格一样使用它们,输入内容,结果在中间没有任何魔法,只是直接在MS中已经设计并测试过的框架类中找到:P,你可以依赖它上。

如果你正在使用扩展方法做一些繁重的工作,我会再次看看你的程序设计,查看你的CRC设计,类模型,用例,DFD的,行动图或任何你喜欢使用,并找出在哪里在这个设计中,你计划把这些东西放在扩展方法中而不是适当的类中。

在一天结束时,您只能根据您的系统设计进行测试,而不是在范围之外编写代码。如果你打算使用扩展类,我的建议是查看对象组合模型,而只有在有很好的理由时才使用继承。

对象组合总是与我一起胜出,因为它们产生可靠的代码。你可以插入它们,拿出来,做你喜欢的东西。注意这一切都取决于你是否使用接口或不作为你的设计的一部分。另外,如果使用组合类,则类层次结构树将变为分散的类,并且您的扩展方法将通过继承类拾取的位置较少。

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