不久前,我们发布了这个有趣的游戏,我们称之为Spring API Bingo 。 当形成有意义的类名时,它是对Spring的巨大创造力的赞美和奉承。
- FactoryAdvisorAdapterHandlerLoader
- ContainerPreTranslatorInfoDisposable
- BeanFactoryDestinationResolver
- LocalPersistenceManagerFactoryBean
上面的两个类实际上存在。 你能发现他们吗? 如果没有,请玩Spring API Bingo !
显然,Spring API遭受...
命名事物
在计算机科学中只有两个难题。 缓存失效,命名和错误一处
– Tim Bray引用Phil Karlton
在Java软件中很难摆脱其中的几个前缀或后缀。 考虑一下最近在Twitter上的讨论,这不可避免地导致了(非常)有趣的讨论 :
具有接口:PaymentService实现:PaymentServiceImpl该测试应称为PaymentServiceImplTest而不是PaymentServiceTest
— Tom Bujok(@tombujok) 2014年10月8日
是的, Impl
后缀是一个有趣的话题。 我们为什么要拥有它,为什么我们要一直这样命名呢?
规格与主体
Java是一种古怪的语言。 在其发明之初,面向对象是一个热门话题。 但是过程语言也具有有趣的功能。 当时,一种非常有趣的语言是Ada (以及主要来自Ada的PL / SQL)。 Ada(如PL / SQL)在包中合理地组织了过程和功能,有两种形式:规范和主体。 从维基百科示例 :
-- Specification
package Example isprocedure Print_and_Increment (j: in out Number);
end Example;-- Body
package body Example isprocedure Print_and_Increment (j: in out Number) isbegin-- [...]end Print_and_Increment;begin-- [...]
end Example;
您始终必须这样做,并且两件事的名称完全相同: Example
。 它们存储在两个不同的文件中,分别称为Example.ads
(ad用于表示Ada,s用于说明)和Example.adb
(用于主体)。 PL / SQL遵循该命令,并使用pk作为Package来命名包文件Example.pks
和Example.pkb
。
Java采取了不同的方式,主要是因为多态性和类的工作方式:
- 类既是规范又是主体
- 接口的名称不能与实现类的名称相同(大多数情况下,当然有很多实现)
特别是,类可以是纯规范的混合体,具有部分主体(抽象时)和完整规范和主体(具体时)。
这如何转换为Java命名
并非所有人都对规范和机身的清晰分离感到赞赏,这当然可以争论。 但是,当您处于那种具有Ada风格的思维方式时,那么您可能想要为每个类(至少在公开API的任何地方)都使用一个接口。 我们对jOOQ进行了相同的操作 ,在其中我们建立了以下策略来命名事物:
* Impl
与相应接口成1:1关系的所有实现(主体)都以Impl
为后缀。 如果可能的话,我们尝试将那些实现保留为私有包,并因此密封在org.jooq.impl
包中。 例如:
-
Cursor
及其对应的CursorImpl
-
DAO
及其对应的DAOImpl
-
Record
及其对应的RecordImpl
这种严格的命名方案可以立即清楚地表明,哪个是接口(因此是公共API),哪个是实现。 从这个方面来说,我们希望Java更像Ada,但是我们拥有出色的多态性,并且…
抽象*
…并导致在基类中重用代码。 众所周知,常见的基类应该(几乎)总是抽象的。 仅仅是因为它们最经常是其相应规范的不完整实现(实体)。 因此,我们有很多局部实现,它们也与对应的接口处于1:1关系,并为它们加上Abstract
前缀。 通常,这些部分实现也是私有包的,并密封在org.jooq.impl
包中。 例如:
-
Field
及其对应的AbstractField
-
Query
及其对应的AbstractQuery
-
ResultQuery
及其对应的AbstractResultQuery
特别地, ResultQuery
是扩展 Query
的接口,因此AbstractResultQuery
是扩展 AbstractQuery
的部分实现,后者也是部分实现。
在我们的API中完全实现部分实现是很有意义的,因为我们的API是内部DSL(特定于域的语言) ,因此无论具体Field
实际Substring
如何,都有成千上万种始终相同的方法,例如Substring
默认*
我们做与接口相关的所有API。 在流行的Java SE API中,已经证明这种方法非常有效,例如:
- 馆藏
- 流
- JDBC
- DOM
我们还做与接口相关的所有SPI(服务提供商接口) 。 就API的发展而言,API和SPI之间存在一个本质区别:
- API是由用户消费 ,难以实现
- SPI由用户实现 ,几乎不消耗
如果您不开发JDK(因此没有完全疯狂的向后兼容规则 ),则通常可以安全地向API接口添加新方法。 实际上,我们在每个次要版本中都这样做,因为我们不希望任何人实现我们的DSL(谁想要实现Field
的 286方法或DSL
的 677方法。这太疯狂了!)
但是SPI是不同的。 每当您为用户提供SPI(例如带有*Listener
或*Provider
后缀)时,您都不能简单地向他们添加新方法-至少不是在Java 8之前,因为这会破坏实现,并且其中有很多。
好。 我们仍然这样做,因为我们没有那些JDK向后兼容规则。 我们有更多的放松 。 但是我们建议用户不要直接自己实现接口,而应扩展一个Default
实现,该实现为空。 例如ExecuteListener
和对应的DefaultExecuteListener
:
public interface ExecuteListener {void start(ExecuteContext ctx);void renderStart(ExecuteContext ctx);// [...]
}public class DefaultExecuteListener
implements ExecuteListener {@Overridepublic void start(ExecuteContext ctx) {}@Overridepublic void renderStart(ExecuteContext ctx) {}// [...]
}
因此, Default*
是一个前缀,通常用于提供API使用者可以使用和实例化或SPI实现者可以扩展的单个公共实现,而不会冒向后兼容的风险。 对于Java 6/7缺少接口默认方法,这几乎是一种解决方法,这就是为什么前缀命名更加合适的原因。
此规则的Java 8版本
实际上,这种做法表明,指定Java-8兼容SPI的“好”规则是使用接口,并使所有方法默认为空。 如果jOOQ不支持Java 6,我们可能会这样指定ExecuteListener
:
public interface ExecuteListener {default void start(ExecuteContext ctx) {}default void renderStart(ExecuteContext ctx) {}// [...]
}
*实用程序或*助手
好的,这里是供模拟/测试/覆盖专家和爱好者使用的工具。
为各种静态实用程序方法进行“转储”是完全可以的 。 我的意思是, 您当然可以成为面向对象的警察的成员 。 但…
请。 不要成为“那个家伙”!
因此,有多种识别实用程序类的技术。 理想情况下,您应遵循命名约定,然后再遵守。 例如* Utils 。
从我们的角度来看,理想情况下,您甚至只转储所有未严格绑定到单个类中特定领域的实用程序方法,因为坦率地说,您最后一次欣赏何时必须经历数百万个类才能找到该实用程序方法? 决不。 我们有org.jooq.impl.Utils
。 为什么? 因为它将允许您执行以下操作:
import static org.jooq.impl.Utils.*;
这样,几乎感觉就像您在整个应用程序中都具有“顶级功能”之类的东西。 “全局”功能。 我们认为这是一件好事。 而且我们完全不赞成“我们不能嘲笑这一论点”, 因此甚至不要尝试开始讨论。
讨论区
…或者实际上,让我们开始讨论。 您的技术是什么,为什么? 以下是汤姆·布约克(Tom Bujok)原始Tweet的一些反应,可帮助您入门:
@tombujok号PaymentServiceImplTestImpl!
— Konrad Malawski(@ktosopl) 2014年10月8日
@tombujok摆脱了界面
— Simon Martinelli(@simas_ch) 2014年10月8日
@tombujok Impl一切!
— Bartosz Majsak(@majson) 2014年10月8日
@tombujok @lukaseder @ktosopl根本原因是该类*不应*称为* Impl,但我知道您无论如何一直在拖钓我们
—彼得·科夫勒(@codecopkofler) 2014年10月9日
我们走吧 !
翻译自: https://www.javacodegeeks.com/2014/10/the-dreaded-defaultabstracthelperimpl.html