【问题标题】:Prefixing interfaces with I?在接口前加上 I?
【发布时间】:2011-08-14 14:07:38
【问题描述】:

我目前正在阅读 Rober Martin (UncleBob) 的“清洁代码”,并且总体上喜欢 UncleBob 的沉思。但是,当我读到他避免为“IPerson”之类的接口添加前缀时,我有点困惑。他说“我不希望我的用户知道我正在给他们一个界面”。

从 TDD/注入的角度思考,我总是很想告诉我的类的“用户”我正在处理一个接口。主要原因是我认为系统的不同“代理”之间的接口契约。一个代理在我系统的一个角落工作,不应该知道另一个代理工作的具体实现;他们应该只交换合同,并期望在不知道如何履行的情况下履行合同。另一个也是非常重要的原因是接口可以完全模拟,从而使单元测试更容易。你可以在一个具体的类上模拟多少是有限制的。

因此,我更喜欢想象我确实在处理一个接口......或者将一个接口作为参数。但是由于 UncleBob 是我们社区中的重量级冠军,而我只是另一个蝇量级的桌面骑师,我想知道我是否遗漏了什么。

我坚持我在接口中是错误的吗??

【问题讨论】:

  • 我认为这是一个偏好问题。虽然,在大多数语言中,在接口前加上 I 是一种惯例。
  • @Kevin 大多数语言?
  • @Cumbayah 应该更精确一点。 大部分我实际上是指:Java、C# 和 VB.NET。
  • @Kevin 实际上,Java 类库中的标准是不为接口添加前缀(而是在必要时在具体类上添加 Impl 后缀)。
  • @Kevin, @Cumbayah:I-Prefix 约定起源于微软在 C++ 中的 COM,并且被广泛使用。这就解释了为什么尽管它不在 Sun 的代码约定中(大多数 Java 程序员都遵循),但它在 Java 中得到了一些关注,以及为什么它是 .Net 的官方约定

标签: unit-testing interface dependency-injection tdd coding-style


【解决方案1】:

如果您谈论的是 .NET,那么开头与 I 的接口是如此普遍,以至于放弃它们会使每个人都感到困惑。

另外我宁愿拥有

public class Foo : IFoo {}

public class FooImpl : Foo {}

这一切都归结为个人喜好,我自己尝试了一段时间,但我又回到了I 前缀。 YMMV

【讨论】:

  • 我同意后缀Impl 很糟糕,但你可以使用其他东西。例如:我更喜欢class SystemClock : Clock 而不是class Clock : IClock。甚至class DefaultClock : Clock。是的,我不喜欢 I 后缀。
  • 但是当你的接口有一个实现(依赖倒置),既不能命名为SystemClock也不能命名为DefaultClock,只能命名为Clock,如何处理呢?例如, UserRepository : IUserRepository
【解决方案2】:

Java 和 C# 中有许多我们已经习惯的约定;但那是倒退的。例如,从技术角度来看,将私有变量放在每个类的顶部的约定是非常愚蠢的。一个类最重要的是它的公共方法。 最不重要的东西,我们在隐私屏障后面隐藏的东西,是实例变量。那么我们为什么要把它们放在顶部呢?

接口前面的“I”是另一个向后的约定。当你被传递一个对一个对象的引用时,你应该期望它是一个接口。接口应该是默认的;所以没有必要做一些额外的事情,比如使用 I 前缀,来宣布你正在做每个人都希望你做的事情。如果我们为传递具体类的异常条件保留一个特殊标记会更好(尽管仍然是错误的)。

使用 I 的另一个问题是(奇怪的是)我们使用它来传达使用接口的实施决策。通常我们不希望如此大声地表达实施决策,因为这会使它们难以改变。例如,考虑一下,如果您决定 IFoo 真的应该是一个抽象类而不是一个接口,可能会发生什么。您应该将名称更改为 Foo 或 CFoo 还是 ACFoo?

我能听到轮子在你脑海中转动的声音。你在想:“是的,但是接口在语言中占有特殊的位置,所以用特殊的命名约定来标记它们是合理的。”确实如此。但是整数在语言中也有一个特殊的位置,我们不再标记它们。此外,问问自己这个问题,为什么接口在语言中占有特殊的位置?

Java 和 C# 接口背后的整个想法是一种逃避。语言设计者本可以使用抽象类,但他们担心实现多重继承的困难。所以他们和自己做了一个幕后交易。他们发明了一种人工构造(即接口),可以提供一些多重继承的力量,并且他们将普通类限制为单继承。

这是语言设计者做出的最糟糕的决定之一。他们发明了一种新的重量级语法元素,以排除一个有用且强大(尽管有争议)的语言特性。接口不是为了启用而发明的,它们是为了禁用而发明的。接口是不想解决 MI 的更难问题的设计人员在语言中放置的一种技巧。因此,当您使用 I 前缀时,您将重点放在语言历史上最大的黑客攻击之一上。

下次你写这样的函数签名时:

public void myFunction(IFoo foo) {...}

问问自己这个问题:“我为什么想知道 IFoo 的作者使用了‘interface’这个词?他使用‘interface’或‘class’甚至‘struct’对我有什么不同?那是他的事,不是我的事! 那么他为什么要强迫我知道他的事,把这个伟大的我放在他的类型名称前面呢?他为什么不把他的声明拉上拉链,把他的隐私保密我的脸?”

【讨论】:

  • 我听到你的声音了!!你的观点和其他人的观点正在慢慢开始影响我的脑电波。我也纳闷多年前我放开int、dbl和flt的时候怎么还没放开。
  • 虽然我 100% 同意使用“我”是完全没有必要的,但我无法想象自己很快就会放弃这个习惯。我说不出为什么,但我喜欢这样命名接口。
  • 哇,强者是如何陨落的......我记得在新石器时代,当我还是一名博士生时,世界还很年轻,所有的 PL 教授都为这个新的界面功能感到非常自豪,“解决了“多重继承,现在他们只是一个“黑客”......我只能想到 Stroustrup 对他们说:“我告诉过你”......
  • 这个问答在 SO 上是古老的和离题的,但我不敢相信没有人说接口不仅仅是一种逃避。对于通常无法解决的多重继承问题,它们是一种可接受的折衷方案,无论您采用哪种方式都需要权衡取舍。 IMO 他们应该被允许拥有除字段(现在在 C#8 中)之外的任何东西,但我非常尊重将良好实践内置到语言中的设计态度(无状态抽象类),并彻底禁止有问题的令人困惑的特性。替代方案(有状态的多继承与组合)。
  • 当你知道它是一个接口你明白你可以传递Foo1,Foo2甚至FooooF,但是当它是一个类或结构你只能传递Foo,所以你应该知道它的业务作者为了使用他的代码,我不能把它当作一个严肃的论点。
【解决方案3】:

我考虑接口合同 在不同的“代理人”之间 系统。一个代理人与一个人一起工作 我系统的一角,应该不知道 另一个的具体实现 代理工作;他们应该只交换 合同,并期望合同 不知道如何实现。这 其他但也很重要的原因 是接口可以被mock 完全,从而进行单元测试 容易得多。怎么做是有限制的 你可以模拟一个具体的类。

所有这些都是正确的 - 但它如何需要接口的命名约定?

基本上,在接口前加上“I”只是另一种无用的匈牙利符号的例子,因为在静态类型语言(接口作为语言结构的唯一一种)中,您总是可以轻松快速地找到找出类型是什么,通常在 IDE 中将鼠标悬停在它上面。

【讨论】:

  • 阿门。声称 I 前缀可以帮助我理解代码,就像声称句子“nounI verbAm properCumbayah”更容易理解一样。从本质上讲,编程是关于创建一种用于解决问题域的语言,而那种实现级别的混乱不会为该语言增加任何价值。当我们确实需要查看如此低级的细节时,我们的工具完全能够显示它们。
  • 我知道我可以很容易地找出类型是什么。但是将鼠标悬停在一个类型上,以找出我正在处理的内容,对于可读性并没有多大帮助,这是 Clean Code 中的另一个(甚至更多??)重点。面对以 I 为前缀的类型,我本能地知道:A) 存在此接口的任意数量的实现的可能性,B) 系统的其他代理可能正在使用此接口,C) 出于测试目的,我可以存根/模拟这个合同,所以我可以诱导某些行为。这一切都是我从一封小小的信中得到的。也许给我赋予这么多意义是错误的??
  • @Morten:实际上,我想说的是,为了提高可读性,您可以做的最好的事情是避免技术问题。而你的 ABC 点都是客户端代码不应该关心的技术实现细节。
  • @Cumbayah,您好...希望您最后一切顺利 :o) 您是说前缀是多余的吗(然后我要对您的旧接口进行大量重命名,呵呵) ?我突然觉得,也许我应该总是期望传递接口而不是具体的类。鉴于这种假设,我可以理解为什么可以省略前缀。
  • 说得好(+1)......虽然:“在静态类型语言中(唯一一种接口作为语言结构有意义的语言)”这是互联网,我想读者会来举个例子,接口在像 python 这样的动态类型语言中也有意义;)
猜你喜欢
  • 1970-01-01
  • 2019-05-16
  • 2015-08-07
  • 1970-01-01
  • 2013-09-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多