Introduction

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

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

Java 9引入了JPMS,它是一个模块化平台,使用了Java语言规范中的access control概念来强制执行类型的可达性(accessibility)的封装。每个模块定义导出哪些包,因此可由其他模块访问。默认情况下,JMPS层中的模块都驻留在同一个类加载器中。

包可以包含API。API包有两种角色:API consumers and API providers。

在以下设计实践中,我们将讨论包的公共部分。程序包中非public或非protected的成员和类型,在程序包之外是不可访问的,因此它们是程序包的实现细节。

Java包必须是一个内聚,稳定的单元

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

由于包是单元共享,因此其内容必须是众所周知的,并且包含的API仅在兼容方式中随着包在未来版本的发展而变化。这意味着包不能支持API超集或子集;例如,javax.transaction就是一个内容不稳定的包。包的用户必须能够知道包中哪些类型是可用的。这也意味着包应该由单个实体(例如,jar文件)提供,而不是跨多个实体分开,因为包的用户必须知道整个包的存在。

此外,包必须以一种兼容的方式发展。因此,应该对包进行版本控制,并且其版本号必须根据semantic versioning规则进行演变。

但最近我意识到包的主要版本更改的语义版本控制建议是错误的。包演变必须是功能的增加。在语义版本控制中,这增加了次要版本。当您删除功能时,即对包进行不兼容的更改,您必须移动到新的包名称,使原始包仍然兼容。要了解为什么这很重要且必要,请参阅本文Semantic Import Versioning for Go。这两种情况都适用于在对包进行不兼容的更改时转移到新包名而不是更改主要版本的情况。

包间耦合最小化

包中的类型可以引用其他包中的类型。例如,方法的参数类型和返回类型以及字段的类型都可能引用其他包的类型。这种包间耦合创造了所谓的包与包之间的uses关系。这意味着API consumer必须使用与API provider相同的引用包,以便他们理解引用的类型。

通常,我们希望最小化包间耦合以最小化对包的使用约束。这简化了OSGi环境中的布线分辨率,并最大限度地减少了依赖扇出,简化了部署(This simplifies wiring resolution in the OSGi environment and minimizes dependency fan-out simplifying deployment)。

接口比类更受欢迎

对于API,接口比类更受欢迎。这是一种相当常见的API设计实践,对模块化Java也很重要。对接口的实现很自由,一个接口可以有多个实现。接口对于将API consumer与API provider分离是很重要的。它使得一个包含API的包,既可以被API consumer使用,也可以被API provider使用。通过这种方式,API consumer与API provider没有直接的依赖关系。它们都只依赖于API包。

抽象类有时是一种有效的设计选择,但通常接口是首选,特别是考虑到最近接口添加了default  methods这一改进.

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

避免 statics

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

避免静态也是制作可测试API的好方法,因为静态不容易被模拟。

Singletons

有时在API设计中有单例对象。但是,对单例对象的访问不应该像静态一样通过静态getInstance方法或静态字段来访问。当需要单个对象时,该对象应该由API定义为单例,并通过依赖注入或如上所述的对象注册表提供给API consumer。

避免类加载器假设

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

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

java.util.ServiceLoader类,当在JPMS模块中没有使用时,也会受到类加载器假设的影响,因为它假定所有提供者都可以从线程上下文类加载器或提供的类加载器中看到。虽然JPMS允许模块声明声明模块提供或使用ServiceLoader managed service,但在模块化环境中通常不会出现这种假设 .

不要假设永久性

许多API设计只假设一个构造阶段,其中对象被实例化并添加到API中,但忽略了在动态系统中可能发生的破坏阶段。 API设计应该考虑对象可以来,他们可以去。例如,大多数listener API允许添加和删除listener。但是许多API设计只假设添加了对象并且从未删除过。例如,许多依赖注入系统无法撤回注入的对象。

在OSGi环境中,可以添加和删除模块,因此可以适应这种动态的API设计非常重要。该OSGi Declarative Services specification定义了OSGi的依赖注入模型,它支持这些动态,包括注入对象的撤销。

针对provider和consumer划分API

如简介中所述,API包的客户端有两个角色:API consumer和API provider。 API consumer使用API,API provider实现API。对于API中的接口(和抽象类)类型,重要的是API设计清楚地记录哪些类型仅由API provider实现,而API consumer不可以实现。为了方便记忆,我们把API provider需要实现的部分记为P,把API consumer需要实现的部分记为C。例如,侦听器接口通常由API consumer实现,并且实例传递给API provider。

API provider对API 中P部分和C部分更改都很敏感。API provider必须实现API中P部分的类型的任何新更改,并且必须了解C部分的任何新更改。 API consumer通常可以忽略API中P部分的更改,除非它想要更改以调用新函数。但API consumer对API中C部分的更改很敏感,可能需要修改才能实现新功能。例如,在javax.servlet package, ServletContext由API provider(如servlet容器)实现。为ServletContext添加新方法将要求更新所有API provider以实现新方法,但API consumer不必更改,除非他们希望调用新方法。然而Servlet由API consumer实现,为Servlet添加新方法将要求修改所有API consumer以实现新方法,并且还需要修改所有API provider以使用新方法。就这样ServletContext类似于API的P部分,Servlet类似于API中C部分。

由于通常有许多API consumer和很少的API provider,因此在考虑更改API 中C部分时,API演变必须非常小心。这是因为,您需要更改少数API provider以支持更新的API,但您不希望在更新API时更改许多现有API consumer。 API consumer只需要在API consumer想要利用新API时进行更改。

Conclusion

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

java 最小化 api_Java的API设计实践相关推荐

  1. java api 设计_Java API设计实践

    使你的API在模块化和非模块化Java环境中都可用 在优锐课的java学习分享中,对微服务有了更深层次的新概念.关于API设计实践一点就通了. 介绍 了解设计Java API时应应用的一些API设计实 ...

  2. JAVA中台化与常用组件设计

    常见组件与中台化 1. 中台概述 1.1. 中台概念 随着互联网公司的崛起,"中台"这个词也进入了人们的视线.BAT 等公司纷纷推出了自己的中台系统.那么,什么是中台系统? 任何一 ...

  3. android组件设计,Android组件化开发路由的设计实践

    调研了一下目前的路由框架,ARouter(阿里的),ActivityRouter都使用了apt技术 编译时注解,个人想法是一口吃不成胖子,先做个比较实用的. VpRouter路由框架主要应用于组件化开 ...

  4. java正则表达式及api_JAVA常用API:正则表达式regular expression

    一.正则表达式的概念 正则表达式,regular expression,在代码中通常简写成regex 正则表达式是一个字符串,使用每单个字符串来描述.定义匹配规则,匹配一系列符合某个语法规则的字符串. ...

  5. java class 静态模块_Java API 最佳设计实践:在模块化和非模块化 Java 环境中使用...

    了解在设计 Java API 时应该运用的一些 API 设计实践.这些实践通常很有用,而且可确保 API 能在诸如 OSGi 和 Java Platform Module System (JPMS) ...

  6. AOL架构原则.优秀API设计.Yeoman工具

    本期的架构周报主要关注AOL(美国在线)的高可用性架构.技术专家Joshua Bloch对优秀API的设计观点.新的Web应用开发工具集Yeoman和OpenStack网络项目Neutron的介绍. ...

  7. 有点长的 Java API 设计清单

    在设计Java API的时候总是有很多不同的规范和考量.与任何复杂的事物一样,这项工作往往就是在考验我们思考的缜密程度.就像飞行员起飞前的检查清单,这张清单将帮助软件设计者在设计Java API的过程 ...

  8. Java API 设计清单

    为什么80%的码农都做不了架构师?>>>    在设计Java API的时候总是有很多不同的规范和考量.与任何复杂的事物一样,这项工作往往就是在考验我们思考的缜密程度.就像飞行员起飞 ...

  9. 【微服务架构】在微服务架构中最小化设计时间耦合

    理查森:我是克里斯·理查森.欢迎来到我关于在微服务架构中最小化设计时耦合的演讲.在这次演讲中,我将回答三个问题.什么是设计时耦合?这会造成什么问题?我们如何设计松散耦合的服务?这些年来我做了一些事情. ...

最新文章

  1. linux dev controlC0,关于Linux的alsa音频问题解决
  2. 植物数据库-小RNA注释数据库 sRNAanno(2021)
  3. Linux运维之网络运维
  4. Java NIO框架Netty教程(一) – Hello Netty
  5. Jzoj4840 小W砍大树
  6. 7天拿到阿里Android岗位offer,都是精髓!
  7. memcached telnet命令
  8. Matlab中图像函数大全
  9. linux磁盘常用操作命令
  10. mysql show processlist
  11. 乐华网上阅卷系统服务器地址,乐华网上阅卷系统
  12. Unity 动态更改鼠标样式
  13. uc/os-II的内存改进与实现TLSF算法的详解,移植实现(四)
  14. [AndroidO] [RK3399] -- 支持 CH341 驱动 -- 驱动模块的标准添加流程
  15. win7鼠标指针主题包_轻松办公之全局鼠标手势软件
  16. 360大战QQ,用户被“保护”还是被“偷窥”
  17. Xcode6 app沙盒目录
  18. Html Table 合并单元格
  19. linux 声音控制 命令,命令行下调整声音大小
  20. 苹果吃鸡蓝牙耳机推荐

热门文章

  1. xcache php5.3,CentOS 配置 xcache for php.5.3.3
  2. js判断数组中重复元素并找出_面试中常遇见的数组去重
  3. javascript 实现图片切换,考虑平稳退化与行为结构分离
  4. 我来阅读lodash源码——Math(一)
  5. 继续过中等难度.0309
  6. 使用复合索引代替单键索引,来避免单键有null值的情况
  7. 进程间通信(三)—信号量
  8. Stylus插件开发教程
  9. OpenGL基础知识
  10. Init进程和进程 ④