常见的 Java Interface 错误用法

在 Java 专案中,应该不少人看过或写过只有一个实作(implementation)的介面 (interface),并且以 interface-impl 的风格成对出现,如下图的 FooImpl, BarImpl, ServiceImpl:

kaisheng714.github.io-常见的 Java Interface 错误用法

虽然此写法可以在任何时候用另一个实作来替换原本的实作,提高程式码的弹性。但我认为如果当前没有多个实作,这反而会增加额外的工作量,是一种过度设计,可能导致下列几个问题:

问题

很多人会写 interface-impl 的原因是根据经典的设计原则或设计模式,认为程式应依赖抽象介面,于是他们事先写出许多 interface、提早抽象化,以便于未来替换不同实作方式,增加程式弹性。然而,现实世界中充满了变数,专案随着时间推移,许多过早预想的设计最终很可能无法实现。(YAGNI 原则)

此外,如果在专案中,当前的 interface 只存在唯一的 implementation,换句话说,此时的 interface 其实并不是一个抽象概念,反而具体到某种程度,这往往造成过度设计的问题,并可能导致后续接手维护程式的人感到困难。例如每当 interface-impl 之一发生改变时,无论是新增功能、重构、改变名称或其他修改,都需要额外的工作量来同步另一方。在开发中,我们应该减少重複的工作,特别是重複维护程式码。(DRY 原则)

在较大的专案中,如果存在许多 interface-impl,IDE 可能会妨碍进行 trace code,因为 IDE 会不断询问要导向至何者,这也会降低开发者的工作效率与心理感受,而成对出现的 interface-impl 表示档案数量是比原本多一倍的,这不仅让专案虚胖,也增加了複杂度,变得不那么直观。

如何改善?

我认为这种情况下,直接使用具体类别是合理的,因为保持程式码的简单直观非常重要。有些人可能会觉得这样写也无伤大雅,但大问题通常源于小问题,最终很可能成为令众人束手无策的历史共业。

如果需要为单元测试而使用 interface,我建议可以使用模拟(mocking)函式库,或者利用继承、@Override或模拟(faking)技术在测试中替换具体实作,这样就不需要特别写 interface,同时也能保持专案的简洁性。

因此,我建议开发者在不确定是否需要 interface 时,可以先暂时不要。在现代强大的 IDE 的帮助下,可以在确定需要时随时进行「extract interface」,几乎没有额外的成本。因此,改善这个问题的方法很简单:延迟决定,并通过持续反馈和迭代,及时调整和改进设计。

interface 的建议用法

一般而言,使用 interface 的目的是实现多型,以提高程式码的灵活和可维护性。其中一种常见的用法是透过 property,可以在 runtime 时根据不同环境动态选择使用哪种 implementation。

对于开发函式库、SDK 等需要提供给外部专案使用的情况,也很适合运用 interface 定义系统边界,这个方式可以让外部 client 透过 interface 来整合与使用。开发者只要在设计时专注于提供规格,且可以不必在乎 client 如何实作,只需要求他们符合 interface 的规範即可。

另一方面,在实务上,若专案程式码不对外开放(例如企业应用程式),或者不使用必须写 interface 的框架(如微服务、ORM等),则需要使用 interface 的情况可能较少。

结语

虽然 interface 可以提高程式码的灵活性和可维护性,如果程式在设计时就有很具体的行为,而且目前也不会有不同实作,那其实可不必写 interface。

虽然经典的设计原则鼓励程式之间应依赖抽象介面,但依赖具体类别也并不是错,因此我建议开发者应根据专案的情况,权衡是否需要 interface,并且遵循最佳实践。

Reference

本文转录自我的部落格 https://kaisheng714.github.io/articles/anti-pattern-of-java-interface-impl-style

更多你可能会感兴趣的文章

软体设计原则 YAGNI (You aren't gonna need it!)软体设计原则 DRY (Don't repeat yourself)

关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章