热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

简单学习JavaAPI设计实践

API(ApplicationProgrammingInterface,应用程序编程接口)是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件的以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节。需要的可以了解一下

前言

了解在设计 Java API 时应该运用的一些 API 设计实践。这些实践通常很有用,而且可确保 API 能在诸如 OSGi 和 Java Platform Module System (JPMS) 之类的模块化环境中得到正确使用。有些实践是规定性的,有些则是禁止性的。当然,其他良好的 API 设计实践也同样适用。

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

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

一个包可以包含一个 API。这些 API 包的客户端有两种角色:API 使用者和 API 提供者。API 使用者使用 API 提供者实现的 API。

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

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

Java 包的设计必须确保它是一个有凝聚力且稳定的单元。在模块化 Java 中,包是模块之间共享的实体。一个模块可以导出一个包,以便其他模块可以使用这个包。因为包是在模块之间共享的单元,所以它必须有凝聚力,因为包中的所有类型必须与包的特定用途相关。

不鼓励使用混杂的包,比如 java.util,因为这种包中的类型通常是彼此不相关的。这类没有凝聚力的包可能导致大量依赖项,因为包的不相关部分会引用其他不相关的包,而且对包的某个方面的更改会影响依赖于此包的所有模块,即使模块可能并未实际使用该包的被修改部分。

由于包是一个共享单元,所以它的内容必须是众所周知的,而且随着包在未来版本中的演变,只能以兼容方式更改所包含的 API。这意味着包不能支持 API 超集或子集;例如,可以将 javax.transaction 视为内容不稳定的包。

包的用户必须能够了解包中提供了哪些类型。这也意味着包应由单个实体(例如一个 jar 文件)提供,不得跨多个实体进行拆分,因为该包的用户必须知道整个包的存在。

此外,该包必须以兼容方式实现在未来版本中的演变。因此,应该对包进行版本控制,而且其版本号必须根据语义版本控制规则进行演变。

但最近我意识到,针对包的主版本更改的语义版本控制建议是错误的。包的演变必须是功能的增加。在语义版本控制中,这会增加次要版本。

当删除功能时,您会对包执行不兼容的更改,而不是增加主版本, 您必须改用一个新的包名,而让原始包保持可兼容。在对包执行不兼容的更改时,应该改用新的包名,而不是更改主版本。

最小化包耦合

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

一般而言,我们希望最小化这种包耦合,以便最小化包上的使用限制。这可以简化 OSGi 环境中的连接解析,并最大限度地减少依赖扇出,从而简化部署。

接口优先于类

对于 API,接口优先于类。这是一种相当常见的 API 设计实践,对模块化 Java 也很重要。接口的使用提高了实现的自由度,还支持多种实现。

接口对于让 API 使用者与 API 提供者分离至关重要。无论是实现接口的 API 提供者,还是调用接口上的方法的 API 使用者,都允许使用包含 API 接口的包。

通过这种方式,API 使用者不会直接依赖于 API 提供者。它们都只依赖于 API 包。

除接口外,抽象类有时也是一种有效的设计选择,但接口通常是首选,尤其是考虑到接口的最新改进允许添加 default 方法。

最后,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 服务注册表之类的对象注册表来完成。请参见白板模式。

在 JPMS 模块中不使用 java.util.ServiceLoader 类时,也会受到类加载器假设的影响,因为它假设所有提供者都对线程上下文类加载器或所提供的类加载器可见。

此假设在模块化环境中通常是不成立的,但 JPMS 允许通过模块声明来声明模块提供或使用了一个 ServiceLoader 托管服务。

不进行持久性假设

许多 API 设计都假设只有一个构造阶段,对象在该阶段被实例化并添加到 API,但是忽略了动态系统中可能发生的解构阶段。

API 设计应考虑到,对象可以添加,也可以删除。例如,大多数监听器 API 都允许添加和删除监听器。但是,许多 API 设计仅假设对象可以添加,并且从未删除这些对象。例如,许多依赖注入系统无法撤销注入的对象。

在 OSGi 环境中,可以添加和删除模块,因此能够兼顾到此类动态的 API 设计非常重要。OSGi Declarative Services 规范为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 使用者类型的更改很敏感,而且可能需要修改才能实现新功能。

例如,在 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 Alliance 定义了文档注释、ProviderType 和 ConsumerType 来标记 API 包中的类型角色。这些注释包含在 osgi.annotation jar 中供您的 API 使用。

结束语

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


推荐阅读
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • Principle for Mac(交互式屏幕设计软件)免激活版
    Mac上好用的交互式屏幕设计软件,PrincipleforMac是一款交互式屏幕设计软件,principle mac让您的设计将以原则出现,随时为您注入新的活力。如果您进行更改,再 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • docker增加restart=always, docker重启后自动启动容器的方法
    本文介绍了在运行docker容器时如何添加参数来保证每次docker服务重启后容器也自动重启的方法,以及如何使用命令来更新已启动的容器。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • 处理docker容器时间和宿主机时间不一致问题的方法
    本文介绍了处理docker容器时间和宿主机时间不一致问题的方法,包括复制主机的localtime到容器、处理报错情况以及重启容器的步骤。通过这些方法,可以解决docker容器时间和宿主机时间不一致的问题。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • Tomcat/Jetty为何选择扩展线程池而不是使用JDK原生线程池?
    本文探讨了Tomcat和Jetty选择扩展线程池而不是使用JDK原生线程池的原因。通过比较IO密集型任务和CPU密集型任务的特点,解释了为何Tomcat和Jetty需要扩展线程池来提高并发度和任务处理速度。同时,介绍了JDK原生线程池的工作流程。 ... [详细]
  • 本文介绍了Java的集合及其实现类,包括数据结构、抽象类和具体实现类的关系,详细介绍了List接口及其实现类ArrayList的基本操作和特点。文章通过提供相关参考文档和链接,帮助读者更好地理解和使用Java的集合类。 ... [详细]
  • 本文介绍了电流源并联合并的方法,以及谐振电路的原理。谐振电路具有很强的选频能力,通过将电感和电容连接在一起,电流和电压会产生震荡。谐振频率的大小取决于电感和电容的大小,而电路中的电阻会逐渐降低震荡的幅度。电阻和电容组成的电路中,当电容放完电后,电阻两端的电压为0,电流不再流过电容。然而,电感是一种特殊的器件,当有电流流过时,线圈会产生感应磁场,阻止电流的流动,从而使电流不会减小。 ... [详细]
  • 标题: ... [详细]
  • 单点登录原理及实现方案详解
    本文详细介绍了单点登录的原理及实现方案,其中包括共享Session的方式,以及基于Redis的Session共享方案。同时,还分享了作者在应用环境中所遇到的问题和经验,希望对读者有所帮助。 ... [详细]
  • 本文介绍了在Docker容器技术中限制容器对CPU的使用的方法,包括使用-c参数设置容器的内存限额,以及通过设置工作线程数量来充分利用CPU资源。同时,还介绍了容器权重分配的情况,以及如何通过top命令查看容器在CPU资源紧张情况下的使用情况。 ... [详细]
  • Python SQLAlchemy库的使用方法详解
    本文详细介绍了Python中使用SQLAlchemy库的方法。首先对SQLAlchemy进行了简介,包括其定义、适用的数据库类型等。然后讨论了SQLAlchemy提供的两种主要使用模式,即SQL表达式语言和ORM。针对不同的需求,给出了选择哪种模式的建议。最后,介绍了连接数据库的方法,包括创建SQLAlchemy引擎和执行SQL语句的接口。 ... [详细]
author-avatar
王耀
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有