Java和C#之间存在一个微小的差异,在这里是相关的。在Java中,默认情况下,每个成员都是虚拟的。在C#中,默认情况下每个成员都是密封的-接口成员除外。
与此相关的假设会影响准则-在Java中,根据Liskov的“替代原则” [1],每个公共类型都应视为非最终类型。如果只有一个实现,则将其命名为class Parser
。如果发现需要多个实现,则只需将类更改为具有相同名称的接口,然后将具体实现重命名为描述性名称。
在C#中,主要假设是当您获得一个类(名称不以开头I
)时,这就是您想要的类。请注意,这远远不能达到100%的准确度-一个典型的反例就是类似的类Stream
(它确实应该是一个接口或几个接口),并且每个人都有自己的使用其他语言的指南和背景。还有其他例外,例如使用相当广泛的Base
后缀来表示抽象类-就像具有接口一样,您知道类型应该是多态的。
还有一个很好的可用性功能,它为与该接口相关的功能保留了非I前缀的名称,而不必诉诸使该接口成为抽象类(由于C#中缺少类多重继承,这会造成伤害)。这被LINQ所普及,它IEnumerable<T>
用作接口以及Enumerable
适用于该接口的方法的存储库。在Java中,接口也可以包含方法实现,这是不必要的。
最终,该I
前缀在C#世界中广泛使用,并在.NET世界中得到扩展(由于到目前为止,大多数.NET代码都是用C#编写的,因此对于大多数公共接口,都应遵循C#准则)。这意味着您几乎可以肯定会使用遵循该符号的库和代码,并且采用这种传统来防止不必要的混乱是很有意义的-就像省略前缀不会使您的代码变得更好:)
我认为鲍勃叔叔的推理是这样的:
IBanana
是香蕉的抽象概念。如果有任何实现类没有比更好的名称Banana
,那么抽象是完全没有意义的,您应该删除接口并仅使用一个类。如果有一个更好的名称(例如LongBanana
或AppleBanana
),则没有理由不使用它Banana
作为接口的名称。因此,使用I
前缀表示您有一个无用的抽象,这使代码更难理解,没有任何好处。而且由于严格的OOP总是让您针对接口进行编码,因此唯一看不到I
类型前缀的地方就是构造函数-毫无意义。
如果将其应用于示例IParser
接口,则可以清楚地看到抽象完全在“无意义”的范围内。无论是有一些具体的事情有关的具体实现解析器(例如JsonParser
,XmlParser
...),或者你应该只使用一个类。没有“默认实现”这样的东西(尽管在某些环境中,这确实有意义-尤其是COM),或者有特定的实现,或者您想要“默认值”的抽象类或扩展方法。但是,在C#中,除非您的代码库已经省略了I
-prefix,否则请保留它。每当您看到类似的代码时class Something: ISomething
,都需要在脑子里记一下-这意味着有人不太擅长遵循YAGNI并构建合理的抽象。
[1]-从技术上讲,这在Liskov的论文中没有特别提及,但这是原始OOP论文的基础之一,在我阅读Liskov时,她没有对此提出质疑。在一种不太严格的解释中(大多数OOP语言采用的一种解释),这意味着使用打算用于替代(即非最终/密封)的公共类型的任何代码都必须与该类型的任何符合实现一起工作。