【问题标题】:Java Interfaces/Implementation naming convention [duplicate]Java接口/实现命名约定[重复]
【发布时间】:2011-02-18 09:05:13
【问题描述】:

您如何命名您创建的不同类/接口? 有时我没有要添加到实现名称中的实现信息——例如接口FileHandler 和类SqlFileHandler

发生这种情况时,我通常将接口命名为“普通”名称,例如 Truck,并将实际类命名为 TruckClass

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

【问题讨论】:

  • 你不调用接口ITruck,类Truck
  • 不好的约定是不好的,不管是谁提出来的,我读到愚蠢的 Impl 后缀首先出现在 IBM Developer Works 文章中。
  • @Robert Harvey,我主要来自 C# 背景,所以我完全同意您的评论。似乎对于Java来说,共识是一个非常不同的约定,从user177800的回答中可以看出

标签: java naming-conventions


【解决方案1】:

将您的Interface 命名为它是什么。 Truck。不是ITruck,因为它不是ITruck,而是Truck

Java 中的InterfaceType。然后你有DumpTruckTransferTruckWreckerTruckCementTruckimplements Truck

当您使用Interface 代替子类时,您只需将其转换为Truck。如List<Truck>。将I 放在前面只是Hungarian style 符号tautology,它只会为您的代码添加更多内容。

所有现代 Java IDE 的标记接口和实现以及没有这个愚蠢的符号。不要称它为TruckClass,那是tautology,就像IInterface 重言式一样糟糕。

如果它是一个实现,它就是一个类。这条规则唯一真正的例外,而且总是有例外,可能是AbstractTruck。由于只有子类会看到这一点,并且您永远不应该转换为 Abstract 类,它确实添加了一些关于该类是抽象的信息以及应该如何使用它。您仍然可以想出一个比AbstractTruck 更好的名称并使用BaseTruckDefaultTruck,因为abstract 在定义中。但是由于Abstract 类永远不应该是任何面向公众的接口的一部分,我相信这是一个可以接受的规则例外。让构造函数protected 可以跨越这个鸿沟。

Impl 后缀也只是更多的噪音。更多的重言式。任何不是接口的东西都是实现,即使是部分实现的抽象类。你要在每个Class 的每个名字上加上那个愚蠢的Impl 后缀吗?

Interface 是关于公共方法和属性必须支持的合同,它也是Type 信息。实现Truck 的所有内容都是TypeTruck

查看 Java 标准库本身。你看到IListArrayListImplLinkedListImpl了吗?不,您会看到 ListArrayListLinkedList。这是一个很好的article 关于这个确切的问题。任何这些愚蠢的前缀/后缀命名约定都违反了DRY 原则。

另外,如果您发现自己在对象中添加了DTOJDOBEAN 或其他愚蠢的重复后缀,那么它们可能属于package,而不是所有这些后缀。正确打包的命名空间是自我记录的,并减少了这些构思拙劣的专有命名方案中的所有无用冗余信息,大多数地方甚至内部都没有以一致的方式遵守。

如果您想使您的 Class 名称独一无二的方法就是在其后缀上加上 Impl,那么您需要重新考虑使用 Interface。因此,当您遇到Interface 和单个Implementation 不是从Interface 唯一专门化的情况时,在大多数情况下您可能不需要Interface

但是,一般来说,为了可维护性、可测试性、模拟,最好的做法是提供接口。见this answer for more details

另请参阅 Martin Fowler 关于 InterfaceImplementationPair 的这篇有趣文章

【讨论】:

  • 如果您有不同意此答案的答案,请发布。 cmets 已经展开了广泛的讨论,这有点超出了他们的控制范围。
  • 最后一条关于单一实现的评论是错误的。接口仍然是一个不错的选择,也许可以与同事分担工作,甚至可以为您的 JUnit 测试创建一个虚拟或虚假的实现。
  • 正确!还有一件事,由于Abstract* 类型不添加新合同,它们也应该被认为是错误的。拥有抽象类就是违反 SRP。
  • “因此,当您有一个接口和一个不是从该接口唯一专门化的单个实现时,您可能不需要该接口。” — 使用模拟测试时除外。
  • 如果你有一个扩展 Human 并实现 Human 的 John 类。不能让他们俩都是人类。一个需要被称为 HumanInterface,不是吗?
【解决方案2】:

我在这里看到的答案表明,如果您只有一个实现,那么您就不需要接口。这违背了依赖注入/控制反转原则(不要打电话给我们,我们会打电话给你!)。

所以是的,在某些情况下,您希望通过依赖注入的接口实现来简化代码并使其易于测试(也可能被代理 - 您的代码不知道!)。即使您只有两种实现——一种是用于测试的 Mock,另一种是注入实际生产代码的实现——这并不会使接口变得多余。一个有据可查的接口建立了一个契约,它也可以通过一个严格的模拟实现来维护以进行测试。

事实上,您可以建立测试,让 mock 实现最严格的接口契约(为不应为 null 的参数抛出异常等)并在测试中捕获错误,在生产代码中使用更有效的实现(不检查不应该为 null 的参数,因为模拟在您的测试中引发了异常,并且您知道参数不为 null,例如,由于在这些测试之后修复了代码)。

对于新手来说,依赖注入/IOC 可能很难掌握,但是一旦您了解了它的潜力,您就会想在所有地方都使用它,并且您会发现自己一直在制作接口 - 即使只有成为一个(实际生产)实现。

对于这个实现(您可以推断,并且您是正确的,我认为用于测试的模拟应该称为 Mock(InterfaceName)),我更喜欢名称 Default(InterfaceName)。如果出现更具体的实现,可以适当命名。这也避免了我特别不喜欢的 Impl 后缀(如果它不是抽象类,当然它是一个“impl”!)。

我也更喜欢“Base(InterfaceName)”而不是“Abstract(InterfaceName)”,因为在某些情况下,您希望您的基类以后可以实例化,但现在您只能使用“Abstract( InterfaceName)",这会迫使您重命名类,可能会引起一些轻微的混乱 - 但如果它始终是 Base(InterfaceName),则删除抽象修饰符不会改变类的内容。

【讨论】:

  • 控制反转或依赖注入的概念不需要接口来实现类的单一实现,一些流行的 IoC 容器实现迫使你用接口做单一实现的傻事。简单地将对象传递给构造函数就是原始的“依赖注入”。至于模拟,使用 Mockito 之类的东西,然后根据实际类模拟出来。
  • 对,然后当你在实际类中出现bug时,你无法将bug隔离到实际类中。毕竟,类是通过使用来“测试”的。但事实并非如此。您无法通过使用实际代码来实现隔离。当两项测试失败时 - 一项针对班级,一项针对使用该班级的班级,这是错误的吗?通过接口隔离只会导致一个测试失败 - 对实际类的测试,而不是对使用类实现的接口的类的测试。
  • 你不明白 Mockito 对“实际类”做了什么
  • 您不了解共享模拟实现可以多么容易地共享以进行测试重用,而不是使用模拟框架进行不同测试的自定义模拟,每次都通过设置代码重新发明轮子。从长远来看,自定义模拟实现更加清晰,并且具有能够被记录的好处 - 与动态生成的代理不同。此外,除非您在实现类中模拟出 every 方法,否则无法保证被测单元不依赖于实际实现——无论如何它都应该能够工作。
  • 这是我对已接受答案的主要反对意见。即使您当前只有一个混凝土,声明一个接口也是绝对有效的。通过不声明接口,所有消费者都在声明 API 合同,一旦您决定要添加或迁移到新的实现,这些合同就会过时。
【解决方案3】:

接口的名称应该描述接口所代表的抽象概念。任何实现类都应该具有某种特定的特征,可以用来给它一个更具体的名称。

如果只有一个实现类并且你想不出任何使它具体的东西(暗示着想将它命名为-Impl),那么看起来根本没有理由拥有一个接口。

【讨论】:

  • @Jay:这种事情通常由非常正统的人完成,他们对孤立的单元测试一切都非常正统,并且不了解(或访问)允许模拟具体类的模拟框架。
  • @Borgwardt:我认为这是由老 C 程序员完成的,他们认为就像在 C 中他们必须为每个 .c 模块创建一个 .h 一样,认为在 Java 中他们必须创建每个班级都有一个界面! :-)
  • 将类更改为接口会破坏二进制兼容性。如果一个接口只有一个实现,但将来您可能有两个实现(并且向后兼容性很重要),那么仍然将合同提取到接口类型中是明智的。
  • @Nathan:很少有项目需要二进制向后兼容——那些应该有非常明确定义和分离的公共 API。
  • 如果你想提取一个目前只有一个实现的接口,我喜欢尽可能简单地命名接口,然后在实现类前面加上“默认”。因此,在@sdasdadas Notebook 案例中,接口是 Notebook,实现是 DefaultNotebook。我认为这符合 CoC 概念,但如果需要在您的框架中或由依赖于您的框架的应用程序,则允许重写(双关语)Notebook 实现。
【解决方案4】:

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

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

我曾经在 AWT 事件/侦听器/适配器范式之后对我的事件类进行建模。

【讨论】:

    【解决方案5】:

    标准 C# 约定在 Java 中也很有效,它为所有接口添加前缀 I - 因此您的文件处理程序接口将是 IFileHandler,而您的卡车接口将是 ITruck。它是一致的,并且可以很容易地从类中区分接口。

    【讨论】:

    • 这只是 COM 编程的倒退。为什么要从类中区分接口?
    • 在您查看类名并且没有 IDE 为您指出它时帮助它脱颖而出。接口很重要,应该设计得当。
    • 我以前使用 C#,现在使用 Java。我发现 C# 约定更合乎逻辑。这个长线程向您展示了接口和植入需要命名约定,所以说这不是必需的有点居高临下。
    • 如果您正在查看接口代码本身,这是明确的。如果您正在为一个接口搜索一大堆类,或者您正在查看其他人的代码,那么让这些接口分别可见真的很有帮助。
    【解决方案6】:

    我喜欢指示接口描述什么协定的接口名称,例如“Comparable”或“Serializable”。像“卡车”这样的名词并不能真正描述卡车特性——卡车的能力是什么?

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

    【讨论】:

    • 如果模式成立(ComparableSerializable,...),那么它应该是Truckable
    • @Robert Harvey, @Bert F:不,问题是,卡车是做什么的?他们是否在您的特定代码中执行此操作?例如。它们可能是“可骑乘的”。
    • @Mecki - 这是个玩笑 - 我敢肯定@Robert 的评论也是。
    • 这毫无意义,那么List 是什么,您认为应该是Listable 吗?这太荒谬了。
    • @JarrodRoberson - 嗯......有Iterable对吗?我认为问题在于这种可实现的方法仅适用于具有单一行为的接口。更复杂的接口实际上是复合概念,定义了一组相关的行为,例如List 结合了 Addable、Removable、Indexable 等。
    【解决方案7】:

    有些人不喜欢这样,它更像是一个 .NET 约定而不是 Java,但是你可以用大写的 I 前缀来命名你的接口,例如:

    IProductRepository - interface
    ProductRepository, SqlProductRepository, etc. - implementations
    

    反对这种命名约定的人可能会争辩说,您不应该关心您在代码中使用的是接口还是对象,但我发现动态阅读和理解它更容易。

    我不会用“Class”后缀命名实现类。这可能会导致混淆,因为您实际上可以在代码中使用“类”(即类型)对象,但在您的情况下,您不是在使用类对象,您只是在使用一个普通的对象.

    【讨论】:

    • 我们在 Java 中不需要匈牙利符号
    • 没有什么“更容易即时阅读和理解”....如果您在代码中看到“truck.drive()”,为什么还要关心卡车的类型卡车还是 ITruck?知道 Truck 是一个接口的实现,您从中获得了哪些有用的信息?没有。如果有 Truck 类型并且您需要对其进行专门化,那么您就去了解 Truck 是什么
    • @SteveKuo mField 比 ISomething 更傻。
    【解决方案8】:

    我使用两种约定:

    如果接口是一个众所周知的模式(例如 Service、DAO)的特定实例,那么它可能不需要“I”(例如 UserService、AuditService、UserDao)在没有“I”的情况下都能正常工作,因为后缀确定元模式。

    但是,如果你有一次性或两次的东西(通常用于回调模式),那么它有助于将它与一个类区分开来(例如 IAsynchCallbackHandler、IUpdateListener、IComputeDrone)。这些是为内部使用而设计的专用接口,有时 IInterface 会引起人们对操作数实际上是接口这一事实的注意,因此乍一看就很清楚。

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

    【讨论】:

    • 甚至没有一直做得很差:-)
    • 那些“服务”和“DAO”后缀应该是包,那么你也不需要那个无用的后缀。
    • 我真的没有发现包限定在可读性方面澄清了任何事情(尽管从技术上讲它消除了歧义)。考虑一下 hibernate 的 Session,它与那里的所有其他 Session 发生冲突。任何包含两者的代码都是一团糟。
    • 使用包名“DAO”是愚蠢的。这意味着那里唯一的东西是 DAO 类,但实际上我可能也希望我的域对象也在那里。为什么不将您的 Truck、PickupTruck 和 TruckDao 放入您的“卡车”包裹中?事实上,如果你使用 TruckDao 接口,这意味着你以后可以通过不同的方式拉动卡车。不知道你为什么要把它具体化。
    【解决方案9】:

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

    【讨论】:

    • 类不需要后缀来表明它们是接口的实现;这就是implements 关键字的用途。如果我必须在所有班级名称之后看到Impl,我想我会开枪打死自己。
    • 普通并不意味着正确,罗伯特·哈维是 100% 正确的。
    • @Robert Harvey 那么,如果我的接口只有一个实现并且没有具体信息包含在其名称中,我该怎么办?
    • @Robert Harvey 如果界面代表通用车辆并且卡车是车辆的“专业化”,那很好。但这只是卡车公司软件的一个例子。所以在使用界面时应该说卡车。不然就尴尬了。
    • 如果你有一个实现和一个接口,你可能不应该有一个接口。
    猜你喜欢
    • 1970-01-01
    • 2013-12-01
    • 2014-04-30
    • 2013-03-13
    • 2023-04-11
    • 2012-11-27
    • 2016-12-15
    • 2010-10-15
    • 1970-01-01
    相关资源
    最近更新 更多