首页 > Java > java教程 > 正文

Java API 设计实践

WBOY
发布: 2024-08-30 06:02:02
原创
287 人浏览过

API design practices for Java

作者:BJ Hargrave

了解设计 Java API 时应应用的一些 API 设计实践。一般来说,这些实践很有用,并确保 API 可以在模块化环境中正确使用,例如 OSGi 和 Java 平台模块系统 (JPMS)。有些做法是规定性的,有些则是禁止性的。当然,其他良好的 API 设计实践也适用。

OSGi 环境使用 Java 类加载器概念提供模块化运行时来强制类型 可见性 封装。每个模块都有自己的类加载器,它将连接到其他模块的类加载器,以共享导出的包并使用导入的包。

Java 9 中引入的 JPMS 提供了一个模块化平台,使用 Java 语言规范中的访问控制概念来强制执行类型 可访问性 封装。每个模块定义导出哪些包,从而可以被其他模块访问。默认情况下,JMPS 层中的模块都驻留在同一个类加载器中。

包可以包含 API。这些 API 包有两个客户端角色API 使用者API 提供者。 API 消费者使用由 API 提供者实现的 API。

在下面的设计实践中,我们将讨论包的公共部分。包的成员和类型不是公共的或受保护的(即私有或默认可访问的),无法在包外部访问,因此是包的实现细节。

Java 包必须是一个有凝聚力、稳定的单元

Java 包的设计必须确保它是一个内聚稳定单元。在模块化Java中,包是模块之间的共享实体。一个模块可以导出一个包,以便其他模块可以使用该包。因为包是模块之间共享的单位,所以包必须是内聚的,因为包中的所有类型必须与包的特定用途相关。像 java.util 这样的 Grab bag 包是不鼓励的,因为这样的包中的类型通常彼此没有关系。这种非内聚性的包可能会导致大量的依赖关系,因为包的不相关部分引用其他不相关的包,并且对包的某一方面的更改会影响依赖于该包的所有模块,即使模块可能实际上并不使用该包的该部分。已修改的包。

由于包是共享的单位,因此它的内容必须是众所周知的,并且随着包在未来版本中的发展,所包含的 API 只能以兼容的方式进行更改。这意味着包不得支持 API 超集或子集;例如,将 javax.transaction 视为内容不稳定的包。包的用户必须能够知道包中可用的类型。这也意味着包应该由单个实体交付(例如,一个 jar
文件),并且不会拆分为多个实体,因为包的用户必须知道整个包都存在。

此外,该包必须以与未来版本兼容的方式发展。因此,包应该进行版本控制,并且其版本号必须根据语义版本控制规则进行演变。还有一个关于语义版本控制的 OSGi 白皮书。

但是,针对包的主要版本更改的语义版本控制建议是有问题的。包的演变必须是功能的增加。在语义版本控制中,这是增加次要版本。当你删除函数时,这就是对包进行不兼容的更改,而不是增加主要
版本,您必须移动到新的包名称,使原始包仍然兼容。要了解为什么这是重要和必要的,请参阅这篇有关 Go 语义导入版本控制的论文以及 Rich Hickey 在 Clojure/conj 2016 上的精彩主题演讲。这两篇文章都说明了迁移到新包名称而不是更改主要包名称的理由对包进行不兼容更改时的版本。

最小化包耦合

一个包中的类型可以引用其他包中的类型。例如,方法的参数类型和返回类型以及字段的类型。这种包间耦合在包上创建了所谓的 uses 约束。这意味着 API 使用者必须使用与 API 提供者相同的引用包,以便他们都能理解引用的类型。

一般来说,我们希望最小化这种包耦合,以最小化包上的使用限制。这简化了 OSGi 环境中的接线解决方案,并最大限度地减少依赖性扇出,从而简化了部署。

接口优先于类

对于 API,接口优先于类。这是一种相当常见的 API 设计实践,对于模块化 Java 也很重要。接口的使用允许实现自由以及多种实现。接口对于将 API 使用者与 API 提供者解耦非常重要。它允许包含 API 接口的包可供实现接口的 API 提供者和调用接口上的方法的 API 使用者使用。这样,API 使用者就不会直接依赖 API 提供者。它们都只依赖于 API 包。

抽象类有时是一种有效的设计选择,而不是接口,但通常接口是首选,特别是因为可以将默认方法添加到接口中。

最后,API 通常需要许多小的具体类,例如事件类型和异常类型。这很好,但类型通常应该是不可变的,并且不适合 API 使用者进行子类化。

避免静电

API 中应避免静态。类型不应该有静态成员。应避免静态工厂。实例创建应该与 API 解耦。例如,API 使用者应通过依赖项注入或对象注册表(如 OSGi 服务注册表或 JPMS 中的 java.util.ServiceLoader)接收 API 类型的对象实例。

避免静态也是制作可测试 API 的好习惯,因为静态不能轻易模拟。

单例

API 设计中有时会存在单例对象。但是,对单例对象的访问不应通过静态 getInstance 方法或静态字段等静态方法进行。当需要单例对象时,该对象应由 API 定义为单例,并通过如上所述的依赖注入或对象注册表提供给 API 使用者。

避免类加载器假设

API 通常具有可扩展性机制,API 使用者可以提供 API 提供者必须加载的类的名称。然后,API 提供者必须使用 Class.forName(可能使用线程上下文类加载器)来加载该类。这种机制假定从 API 提供者(或线程上下文类加载器)到 API 使用者的类可见性。 API 设计必须避免类加载器假设。模块化的要点之一是类型封装。一个模块(例如,API 提供者)不得对另一模块(例如,API 使用者)的实现细节具有可见性/可访问性。

API 设计必须避免在 API 使用者和 API 提供者之间传递类名,并且必须避免有关类加载器层次结构和类型可见性/可访问性的假设。为了提供可扩展性模型,API 设计应该让 API 使用者将类对象(或者更好的是,实例对象)传递给 API 提供者。这可以通过 API 中的方法或通过对象注册表(例如 OSGi 服务注册表)来完成。请参阅白板图案。

java.util.ServiceLoader 类不在 JPMS 模块中使用时,也会受到类加载器假设的影响,因为它假设所有提供者对于线程上下文类加载器或提供的类加载器都是可见的。尽管 JPMS 允许模块声明来声明模块提供或使用
,但这种假设在模块化环境中通常不成立 ServiceLoader 托管服务。

不要假设永恒

许多 API 设计仅假设对象被实例化并将其添加到 API 的构造阶段,但忽略了动态系统中可能发生的销毁阶段。 API 设计应该考虑对象可以来也可以走。例如,大多数侦听器 API 允许添加和删除侦听器。但许多 API 设计只假设对象被添加,而从未被删除。例如,许多依赖注入系统无法撤回注入的对象。

在 OSGi 环境中,可以添加和删除模块,因此能够适应这种动态的 API 设计非常重要。 OSGi 声明式服务规范
为 OSGi 定义了一个依赖注入模型,它支持这些动态,包括撤回注入对象。

明确记录 API 使用者和 API 提供者的类型角色

正如简介中提到的,API 包的客户端有两种角色:API 消费者和 API 提供者。 API 消费者使用 API,API 提供者实现 API。对于 API 中的接口(和抽象类)类型,重要的是 API 设计必须清楚地记录哪些类型只能由 API 提供者实现,哪些类型可以由 API 使用者实现。例如,监听器接口一般由API消费者实现
以及传递给 API 提供者的实例。

API 提供者对 API 使用者和 API 提供者实现的类型的变化都很敏感。提供者必须实现 API 提供者类型中的任何新更改,并且必须了解并可能调用 API 使用者类型中的任何新更改。 API 使用者通常可以忽略 API 提供者类型的(兼容)更改,除非 API 使用者想要更改以调用新函数。但是 API 使用者对 API 使用者类型的变化很敏感,并且可能需要修改才能实现新功能。例如,在 javax.servlet 包中,ServletContext 类型由 API 提供者(例如 Servlet 容器)实现。向 ServletContext 添加新方法将要求更新所有 API 提供者以实现新方法,但 API 使用者无需更改,除非他们希望调用新方法。然而,Servlet 类型是由 API 消费者实现的,向 Servlet 添加新方法将需要修改所有 API 消费者以实现新方法,并且还需要修改所有 API 提供者以使用新方法。因此,ServletContext 类型具有 API 提供者角色,Servlet 类型具有 API 消费者角色。

由于 API 消费者通常较多而 API 提供者较少,因此 API 演进在考虑 API 消费者类型的更改时必须非常小心,而对于更改 API 提供者类型则要更加宽松。这是因为,您需要更改少数 API 提供程序以支持更新的 API,但您不希望在 API 更新时要求许多现有 API 使用者进行更改。 API 使用者只需要在 API 使用者想要利用新 API 时进行更改。

OSGi联盟定义了文档注释、ProviderType和ConsumerType来标记API包中类型的角色。这些注释可在 osgi.annotation jar 中找到,以便在您的 API 中使用。

结论

下次设计 API 时,请考虑这些 API 设计实践。然后,您的 API 将可在模块化 Java 和非模块化 Java 环境中使用。

以上是Java API 设计实践的详细内容。更多信息请关注PHP中文网其他相关文章!

来源:dev.to
本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责声明 Sitemap
PHP中文网:公益在线PHP培训,帮助PHP学习者快速成长!