介绍了解在设计JavaAPI时应该应用的一些API设计实践。这些实践通常很有用,可以确保API可以在模块化环境(例如OSGi和Java平台模块系统(JPMS))中正确使用。有些做法是规定性的,而另一些则是禁止性的。当然,其他良好的API设计实践也适用。OSGi环境使用Java类加载器概念提供模块化运行时强制类型可见性的封装。每个模块都有自己的类加载器,它链接到其他模块的类加载器以共享导出的包和使用导入的包。Java9引入了JPMS,这是一个模块化平台,它使用Java语言规范中的访问控制概念来强制封装类型可访问性。每个模块都定义了哪些包被导出,从而可以被其他模块访问。默认情况下,JMPS层中的模块都驻留在同一个类加载器中。包可以包含API。API包有两个角色:API消费者和API提供者。在接下来的设计实践中,我们将讨论包的公共部分。包中非公共或受保护的成员和类型在包外不可访问,因此它们是包的实现细节。Java包必须是一个内聚的、稳定的单元Java包的设计必须确保它是一个内聚的、稳定的单元。在模块化Java中,包是模块之间的共享实体。一个模块可以导出一个包,以便其他模块可以使用该包。由于包是模块之间共享的单元,因此包必须具有内聚性,因为包中的所有类型都必须与包的特定用途相关。不鼓励使用像java.util这样的包,因为此类包中的类型通常彼此没有关系。这种非内聚包会导致许多依赖问题,因为包的不相关部分引用其他不相关的包,并且修改包的一部分会影响依赖该包的所有模块,即使模块可能实际上没有使用修改过的这部分.由于包是单元共享的,它的内容必须是众所周知的,并且包含的??API只会随着包在未来版本中的发展而以兼容的方式发生变化。这意味着包不能支持API超集或子集;例如,javax.transaction是一个内容不稳定的包。包的用户必须能够知道包中有哪些类型可用。这也意味着包应该由单个实体(例如,jar文件)提供,而不是拆分为多个实体,因为包的用户必须知道整个包的存在。此外,包必须以兼容的方式发展。因此,包应该是版本化的,它们的版本号必须根据语义版本化规则演变。但最近我意识到包的主要版本更改的语义版本控制建议是错误的。包的演变必须是功能的增加。在语义版本控制中,这会增加次要版本。当您删除功能时,即对包进行不兼容的更改时,您必须移动到新的包名称,以便原始包仍然兼容。要了解为什么这很重要和必要,请参阅这篇文章Go的语义导入版本控制。当移动到新的包名称而不是在对包进行不兼容的更改时更改主要版本时,这两个都适用。包间耦合被最小化包中的类型可以引用其他包中的类型。比如方法的参数类型和返回类型,字段的类型都可能引用其他包的类型。这种包间耦合在包之间创建了所谓的使用关系。这意味着API消费者必须使用与API提供者相同的引用包,以便他们了解引用的类型。通常,我们希望最小化包间耦合以最小化对包的使用限制。ThissimplifiedwiringresolutionintheOSGienvironmentandminimizedependencyfan-outsimplifyingdeployment(这简化了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方法或者像static这样的静态字段来访问。当需要单个对象时,该对象应由API定义为单例,并通过依赖注入或如上所述的对象注册表提供给API使用者。为了避免类加载器假定API通常具有可扩展性机制,API消费者可以提供API提供者必须加载的类的名称。API提供者然后必须使用Class.forName(可能使用线程上下文类加载器)来加载该类。这种机制保证了从API提供者(或线程上下文类加载器)到API消费者的类可见性。API设计必须避免类加载器假设。模块化的一个关键点是类型封装。一个模块(例如,API提供者)不得对另一模块(例如,API消费者)的实现细节具有可见性/可访问性。API设计必须避免在API消费者和API提供者之间传递类名,并且必须避免关于类加载器层次结构和类型可见性/可访问性的假设。为了提供可扩展性模型,API设计应该让API消费者将类对象或更好的实例对象传递给API提供者。这可以通过API中的方法或通过对象注册表(例如OSGi服务注册表)来完成。请参阅白板pattern.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提供者需要实现的部分记为P,API消费者需要实现的部分记为C。比如监听器接口通常由API消费者实现,而实例传递给API提供者。API提供者对API中的P和C变化都很敏感。API提供者必须在API的P部分中实现对类型的任何新更改,并且必须了解C部分中的任何新更改。API消费者通常可以忽略对API的P部分的更改,除非它想要更改以调用新函数。但是,API消费者对API的C部分的变化很敏感,可能需要修改以实现新的功能。例如,在javax.servlet包中,ServletContext是由API提供者(例如servlet容器)实现的。向ServletContext添加新方法将需要更新所有API提供者以实现新方法,但API使用者不必更改,除非他们希望调用新方法。虽然Servlet是由API消费者实现的,但向Servlet添加新方法将需要修改所有API消费者以实现新方法,还需要修改所有API提供者以使用新方法。这样ServletContext类似于API的P部分,Servlet类似于API的C部分。由于通常有很多API消费者和很少的API提供者,因此在考虑更改API的C部分时,API的演进必须非常小心。这是因为,您需要更改少数API提供者以支持更新的API,但您不想在更新API时更改许多现有的API使用者。API消费者只需要在API消费者想要利用新API时进行更改。结论下次设计API时请考虑这些API设计实践。然后,您的API将在模块化Java和非模块化Java环境中可用。英文原文:https://developer.ibm.com/articles/api-design-practices-for-java
