Java接口/实现命名约定

413

如何命名不同的类/接口? 有时我没有实现信息可以添加到实现名称中-例如接口FileHandler和类SqlFileHandler

当出现这种情况时,我通常使用“正常”名称为接口命名,如Truck,然后将实际类命名为TruckClass

您在这方面如何为接口和类命名?


10
你不把接口叫做 ITruck,类叫做 Truck 吗? - Robert Harvey
26
不论是谁提出了不好的规范,它们都是不好的。我读过一篇文章,称愚蠢的“Impl”后缀最初出现在IBM Developer Works的文章中。 - user177800
@Robert Harvey,我主要使用C#,因此我完全同意您的评论。不过,对于Java来说,似乎大家的共识是采用非常不同的约定,这可以从用户177800的答案中看出。 - Chris Halcrow
9个回答

1100

给你的接口命名为其实际名称。比如卡车,而不是I卡车,因为它不是一个I卡车,而是一个卡车

在Java中,接口是一种类型。然后你有自卸卡车运输卡车清障卡车水泥卡车等实现了卡车的类别。

当你使用接口代替子类时,你只需将其转换为卡车,例如List<Truck>。在前面加上I只是系统匈牙利方法的标记冗余性质,这只会增加更多输入代码的工作量,但并没有什么实际意义。

所有现代Java IDE都会标记接口和实现等内容,而不需要这种愚蠢的符号。不要称其为TruckClass,这是tautology,与IInterface的tautology一样糟糕。

如果它是一个实现,那么它就是一个类。唯一真正的例外是,总有例外情况,可能是像AbstractTruck这样的东西。由于只有子类才能看到这个类,并且您永远不应该将其转换为Abstract类,因此它确实添加了一些信息,即该类是抽象的以及如何使用它。您仍然可以想出比AbstractTruck更好的名称,例如使用BaseTruckDefaultTruck,因为abstract在定义中。但是,由于Abstract类永远不应成为任何公共界面的一部分,我认为这是一个可以接受的例外。将构造函数设置为protected可以大大跨越这个差距。

而且Impl后缀只是更多的噪音。更多的同义反复。任何不是接口的东西都是实现,即使是部分实现的抽象类也是如此。你要在每个Class的名称上加上那个愚蠢的Impl后缀吗?

Interface是公共方法和属性必须支持的约定,它也是Type信息。实现Truck的所有内容都是TruckType

看看Java标准库本身。你看到了IListArrayListImplLinkedListImpl吗?没有,你会看到ListArrayList,还有LinkedList。这里有一篇关于这个问题的不错文章。任何这些愚蠢的前缀/后缀命名约定都违反了DRY原则。

另外,如果你发现自己在向对象添加DTOJDOBEAN或其他愚蠢的重复后缀,那么它们可能属于一个,而不是所有这些后缀。正确封装的命名空间是自我说明的,并减少了所有这些真正糟糕的专有命名方案中毫无意义的冗余信息,大多数地方甚至都没有在内部以一致的方式遵守这些命名规范。

如果你只能想到在你的Class名称后面加上Impl来使其独特,那么你需要重新考虑是否需要一个Interface。因此,当你有一个Interface和一个单一的Implementation,它与Interface没有明显的区别时,在大多数情况下你可能不需要Interface

然而,通常为了可维护性、可测试性和模拟,最好提供接口。请参见这个答案以获取更多详细信息

此外,参考Martin Fowler关于InterfaceImplementationPair这个主题的有趣文章。


11
如果您有与此答案不同的答案,请发表它。评论已经演变成了延长讨论,这已经超出了它们的能力范围。 - George Stocker
16
最后一条有关单一实现的评论是错误的。接口仍然是一个不错的选择,也许可以与同事分担工作,甚至为您的JUnit测试创建虚拟或伪装实现。 - amdev
关于DTO我不同意,想象一下“Pet”(实体)和“PetDTO”,好吧它不太优雅但你可以轻易理解代码。如果它们只是在不同的包中,那么它就不太可读了。另外,你的例子适用于接口,但并不总是那么明显,想象一下UserService和DefaultUserService,我使用这种解决方案,但是相对于C#中非常普遍的单个实现的IService和Service来说,它真的更容易理解吗?https://dev59.com/Y3RB5IYBdhLWcg3wQFLu - amdev
13
当你有一个接口和一个单一实现,且该实现并没有从接口中独特地专业化出来,那么你可能不需要这个接口。但是,在使用模拟测试时除外。 - mikhailian
1
如果您拥有一个扩展Human并实现Human的John类,那该怎么办呢?不能让它们都是Human。其中一个需要被称为HumanInterface,是吧? - Liga
显示剩余6条评论

123
我曾经看到有些回答认为,如果只有一个实现,那么就不需要接口。这与依赖注入/控制反转原则相矛盾(不要打电话给我们,我们会给你打电话!)。
因此,在某些情况下,您希望通过依赖注入接口实现来简化代码并使其易于测试(这些实现可能还可以被代理 - 您的代码不知道!)。即使您只有两个实现 - 一个是用于测试的模拟实现,另一个是注入到实际生产代码中的实现 - 这也不会使接口变得多余。良好记录的接口建立了一个契约,也可以由严格的模拟实现进行维护以进行测试。
事实上,您可以建立测试,让模拟实现最严格地实现接口契约(对于不应为null的参数抛出异常等),并在测试中捕获错误,使用更有效的实现在生产代码中(例如,因为模拟在测试中抛出异常并且您知道由于修复这些测试后参数不为空,因此不需要检查不应该为 null 的参数是否为 null)。
对于新手来说,依赖注入/IOC可能很难理解,但是一旦您了解其潜力,您将想要在各个地方使用它,并且您会发现自己一直在创建接口 - 即使只有一个(实际生产)实现。
对于这个唯一的实现(您可以推断,我的确认为测试的模拟应该被称为Mock(InterfaceName)),我更喜欢使用名称Default(InterfaceName)。如果出现更具体的实现,则可以适当地命名它。这也避免了我特别不喜欢的Impl后缀(如果它不是抽象类,那当然是“impl”!)。
我也更喜欢"Base(InterfaceName)"而不是"Abstract(InterfaceName)",因为有些情况下,您希望基类稍后可实例化,但现在您却被困在名称为"Abstract(InterfaceName)"的类中,这迫使您重新命名类,可能会引起一些小混乱-但如果始终是Base(InterfaceName),则删除abstract修饰符不会改变类的本质。

32
关于控制反转或依赖注入的概念,并不需要为单个类的实现使用接口。一些流行的IoC容器实现强制在单个实现中使用接口,这是一种愚蠢的做法。直接将对象传递到构造函数中是最初的“依赖注入”方法。对于模拟操作,可以使用像Mockito这样的工具,基于实际类进行模拟。 - user177800
22
当实际类中存在错误时,您无法将错误隔离到实际类中。毕竟,该类是通过使用它来“测试”的。但实际上并非如此。您不能通过使用实际代码来实现隔离。当两个测试失败时 - 一个是针对类的测试,另一个是使用该类的类的测试,哪个出了问题?通过接口进行隔离只会使一个测试失败 - 实际类的测试,而不是使用类实现的接口的类的测试。 - MetroidFan2002
11
你不理解Mockito对“实际类”所做的操作。 - user177800
17
你可能不了解共享模拟实现可以轻松地在测试中被重用,而不是为不同的测试使用自定义mock并每次通过编写设置代码来重新发明轮子。长期来看,自定义mock实现更加简洁,并且可以被记录 - 不像动态生成代理。此外,除非你模拟实现类中的每个方法,否则不能保证受测单元没有依赖于实际实现 - 它应该能够正常工作。 - MetroidFan2002
6
这是我对被接受的答案的主要反对意见。即使你目前只有一个实现,声明一个接口也是完全有效的。如果不声明接口,所有使用者都在声明API契约,但一旦你决定添加或迁移到新的实现,这些契约就会过时。 - charles-allen
显示剩余5条评论

66
接口的名称应该描述接口所代表的抽象概念。任何实现类都应该具有某种特定特征,可以用来给它一个更具体的名称。
如果只有一个实现类,并且您想不出任何使其具体化的东西(暗示着想要将其命名为“-Impl”),那么看起来没有理由使用接口。

8
通常这种事情是由非常传统的单元测试派完成的,他们会将所有东西都隔离进行单元测试,而且可能不了解(或者无法使用)允许模拟具体类的 mocking 框架。 - Michael Borgwardt
7
我想这是由老一代的C程序员完成的,他们认为就像在C语言中,每个.c模块都需要创建一个.h文件一样,在Java中,每个类都必须创建一个接口! :-) - Jay
44
将一个类改为接口会破坏二进制兼容性。如果只有一个接口的实现,但在未来可能会有两个实现(并且向后兼容性很重要),那么将合约提取为接口类型仍然是明智的选择。 - Nathan
6
@Nathan:非常少的项目需要二进制向后兼容性 - 而且这些项目应该有非常明确定义和分离的公共API。 - Michael Borgwardt
9
如果您想提取一个目前只有一个实现的接口,我喜欢尽可能简单地命名接口,然后使用“Default”前缀来命名实现类。因此,在@sdasdadas Notebook案例中,接口将被命名为Notebook,而实现将被命名为DefaultNotebook。我认为这符合CoC概念,但也允许在需要时重写Notebook的实现,无论是在您的框架中还是在依赖于您的框架的应用程序中。 - Martin Woolstenhulme
显示剩余7条评论

40

我倾向于遵循Java Core/Sun建立的伪标准,例如在Collections类中:

  • List - "概念"对象的接口
  • ArrayList - 接口的具体实现
  • LinkedList - 接口的具体实现
  • AbstractList - 抽象的“部分”实现,用于协助自定义实现。

我曾经采用过同样的方法,将我的事件类建模为AWT事件/监听器/适配器范例。


29

标准的 C# 命名惯例,在 Java 中也适用,是在所有接口前加上 I 前缀——因此,您的文件处理程序接口将是 IFileHandler,而您的卡车接口将是 ITruck。这样做是一致的,并且易于区分接口和类。


44
这只是COM编程的一种遗留问题。为什么要区分接口和类呢? - John Topley
7
当你查看类名且没有指出的IDE时,帮助它脱颖而出。接口很重要,应该被正确设计。 - simgineer
37
我曾经使用过C#,现在使用Java。我觉得C#的命名规则更加合乎逻辑。这个长线程向你展示了接口和实现的命名规范的必要性,所以说不需要这样做有点傲慢。 - Gal Morad
17
如果您查看接口代码本身,那么它是显而易见的。如果您正在搜索一堆类中的一个接口,或者您正在查看别人的代码,将接口分开显示确实有助于更好地理解。 - JamEngulfer
5
逻辑谬误[传统假设/常见实践假设] - 涉及到认为某件事情是正确的或值得做,仅因为它已经被长期接受或广泛实践。这种论证方式忽略了事物本身的合理性和价值,只是简单地依赖于它们的历史背景或普遍存在。 - user177800
显示剩余5条评论

25

我喜欢接口名称表明接口描述的契约内容,例如"Comparable"或"Serializable"。像“卡车”这样的名词并没有真正描述卡车的本质 - 卡车有哪些能力呢?

关于惯例:我曾经参与过每个接口都以"I"开头的项目;虽然这与Java惯例有些不同,但它使查找接口非常容易。除此之外,“Impl”后缀是一个合理的默认名称。


28
如果遵循这种模式(ComparableSerializable,……),那么它应该被称为“Truckable”。 - Bert F
6
不,问题是卡车(Trucks)做什么?在你的特定代码中它们做了什么?例如,它们可以被骑行("Ridable")。 - Mecki
9
@Mecki - 这只是个玩笑 - 我相信 @Robert 的评论也是如此。 - Bert F
6
这没有任何意义,那你建议List应该是Listable吗?那太荒谬了。 - user177800
4
@JarrodRoberson - 嗯...有Iterable对吧?我认为问题在于这种-able的方法只适用于具有单一行为的接口。更复杂的接口实际上是组合概念,定义了一组相关的行为,例如List结合了Addable、Removable、Indexable等功能。 - charles-allen
显示剩余11条评论

12

有些人不喜欢这样做,这更像是.NET的惯例而不是Java的,但你可以给接口命名一个大写字母I前缀,例如:

IProductRepository - interface
ProductRepository, SqlProductRepository, etc. - implementations

反对这种命名惯例的人可能会辩称,在你的代码中,你不应该关心你是在使用接口还是对象,但我发现这样做可以更容易地实时阅读和理解。

我不会给实现类取一个“Class”后缀的名称。那可能会导致混淆,因为你实际上可以在你的代码中使用“class” (即类型)对象,但在你的情况下,你并不在使用类对象,而是在使用普通的对象。


34
Java中不需要使用匈牙利命名法。 - Steve Kuo
3
如果你在代码中看到"truck.drive()",无论这个truck是Truck还是ITruck类型的,都没有什么比它更容易阅读和理解的了。你从知道truck是接口的一种实现中获得了哪些有用的信息?没有任何。如果有一个Truck类型且需要对其进行专门化处理,那么你需要去查找Truck是什么。 - inor
3
@mField 比 ISomething 更傻。 - Moses Aprico
1
这只是逻辑谬误[传统做法/常见做法的诉求](https://en.wikipedia.org/wiki/Appeal_to_tradition) - user177800

1

我同时使用两种命名规范:

如果接口是一个特定的已知模式的实例(例如Service、DAO),那么它可能不需要一个"I"(例如UserService、AuditService、UserDao),因为后缀确定了元模式,这些都可以正常工作。

但是,如果你有一些一次性或两次性的东西(通常用于回调模式),那么将其与类区分开来会有所帮助(例如IAsynchCallbackHandler、IUpdateListener、IComputeDrone)。这些是专门设计用于内部使用的特殊目的接口,偶尔使用"IInterface"会提醒我们操作数实际上是一个接口,因此一眼就能看出来。

在其他情况下,您可以使用"I"来避免与其他常见的具体类发生冲突(ISubject、IPrincipal vs Subject或Principal)。


5
甚至没有始终糟糕地做到 :-) - user177800
1
那些“Service”和“DAO”后缀应该是包,因此您也不需要那个无用的后缀。 - user177800
9
我并不认为包限定符在可读性方面有任何帮助(虽然在技术上可以消除歧义)。考虑 Hibernate 的 Session,它与其他所有 Session 相冲突。任何同时存在这两者的代码都很难理解和阅读。 - Justin
3
使用“DAO”作为包名很傻。它暗示里面唯一的东西是DAO类,但实际上我可能还想把我的域对象放在那里。为什么不把你的Truck、PickupTruck和TruckDao放在你的'Truck'包里呢?事实上,如果你将TruckDao界面化,这意味着以后可以通过不同的方式来获取卡车。不确定为什么要使用具体类。 - Nick

0

TruckClass听起来像是Truck的一个类,我认为推荐的解决方案是添加Impl后缀。在我看来,最好的解决方案是在实现名称中包含一些信息,说明特定实现中正在发生的情况(就像我们在List接口和实现中所拥有的:ArrayListLinkedList),但有时您只有一个实现,并且由于远程使用(例如),必须拥有接口,那么(如开头所提到的)Impl就是解决方案。


26
类不需要后缀来显示它们是接口的实现;这就是 implements 关键字的作用。如果我必须在所有类名后面都看到 Impl,我想我会自杀。 - Robert Harvey
4
“普遍”并不意味着与“正确”相同,Robert Harvey 的说法是完全正确的。 - user177800
2
@Robert Harvey 如果我有一个只有一个实现且没有特定信息可包含在其名称中的接口,那我该怎么办? - Amir Rachum
2
@Robert Harvey 如果接口代表一个通用车辆,而卡车是车辆的“专业化”,那就可以了。但这只是来自卡车公司软件的一个例子。因此,在使用接口时应该写卡车。否则就很尴尬。 - Amir Rachum
1
@JarrodRoberson - 你是说Truck接口可被类化吗?:D - charles-allen
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接