可怕的DefaultAbstractHelperImpl

不久前,我们发布了这个有趣的游戏,我们称之为Spring API Bingo 。 当形成有意义的类名时,它是对Spring的巨大创造力的赞美和奉承。

  • FactoryAdvisorAdapterHandlerLoader
  • ContainerPreTranslatorInfoDisposable
  • BeanFactoryDe​​stinationResolver
  • LocalPersistenceManagerFactoryBean

上面的两个类实际上存在。 你能发现他们吗? 如果没有,请玩Spring API Bingo

显然,Spring API遭受...

命名事物

在计算机科学中只有两个难题。 缓存失效,命名和错误一处

– Tim Bray引用Phil Karlton

在Java软件中很难摆脱其中的几个前缀或后缀。 考虑一下最近在Twitter上的讨论,这不可避免地导致了(非常)有趣的讨论

是的, Impl后缀是一个有趣的话题。 我们为什么要拥有它,为什么我们要一直这样命名呢?

规格与主体

Java是一种古怪的语言。 在其发明之初,面向对象是一个热门话题。 但是过程语言也具有有趣的功能。 当时,一种非常有趣的语言是Ada (以及主要来自Ada的PL / SQL)。 Ada(如PL / SQL)在包中合理地组织了过程和功能,有两种形式:规范和主体。 从维基百科示例

-- Specification
package Example is
  procedure Print_and_Increment (j: in out Number);
end Example;

-- Body
package body Example is
 
  procedure Print_and_Increment (j: in out Number) is
  begin
    -- [...]
  end Print_and_Increment;
 
begin
  -- [...]
end Example;

您始终必须这样做,并且两件事的名称完全相同: Example 。 它们存储在两个不同的文件中,分别称为Example.ads (ad用于表示Ada,s用于说明)和Example.adb (用于主体)。 PL / SQL遵循该命令,并使用pk作为Package来命名包文件Example.pksExample.pkb

Java采取了不同的方式,主要是因为多态性和类的工作方式:

  • 类既是规范又是主体
  • 接口的名称不能与实现类的名称相同(大多数情况下,当然有很多实现)

特别是,类可以是纯规范的混合体,具有部分主体(抽象时)和完整规范和主体(具体时)。

这如何转换为Java命名

并非所有人都对规范和机身的清晰分离感到赞赏,这当然可以争论。 但是,当您处于那种具有Ada风格的思维方式时,那么您可能想要为每个类(至少在公开API的任何地方)都使用一个接口。 我们对jOOQ进行了相同的操作 ,在其中我们建立了以下策略来命名事物:

* Impl

与相应接口成1:1关系的所有实现(主体)都以Impl为后缀。 如果可能的话,我们尝试将那些实现保留为私有包,并因此密封在org.jooq.impl包中。 例如:

这种严格的命名方案可以立即清楚地表明,哪个是接口(因此是公共API),哪个是实现。 从这个方面来说,我们希望Java更像Ada,但是我们拥有出色的多态性,并且…

抽象*

…并导致在基类中重用代码。 众所周知,常见的基类应该(几乎)总是抽象的。 仅仅是因为它们最经常是其相应规范的不完整实现(实体)。 因此,我们有很多局部实现,它们也与对应的接口处于1:1关系,并为它们加上Abstract前缀。 通常,这些部分实现也是私有包的,并​​密封在org.jooq.impl包中。 例如:

特别地, 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 {

    @Override
    public void start(ExecuteContext ctx) {}

    @Override
    public 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的一些反应,可帮助您入门:

我们走吧 !

翻译自: https://www.javacodegeeks.com/2014/10/the-dreaded-defaultabstracthelperimpl.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值