热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

javaapi设计_简单学习JavaAPI设计实践

前言了解在设计JavaAPI时应该运用的一些API设计实践。这些实践通常很有用,而且可确保API能在诸如OSGi和JavaPlatformModuleSystem(J

前言

了解在设计 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 环境中使用。

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



推荐阅读
  • 深入解析Android 4.4中的Fence机制及其应用
    在Android 4.4中,Fence机制是处理缓冲区交换和同步问题的关键技术。该机制广泛应用于生产者-消费者模式中,确保了不同组件之间高效、安全的数据传输。通过深入解析Fence机制的工作原理和应用场景,本文探讨了其在系统性能优化和资源管理中的重要作用。 ... [详细]
  • 使用 ListView 浏览安卓系统中的回收站文件 ... [详细]
  • Web开发框架概览:Java与JavaScript技术及框架综述
    Web开发涉及服务器端和客户端的协同工作。在服务器端,Java是一种优秀的编程语言,适用于构建各种功能模块,如通过Servlet实现特定服务。客户端则主要依赖HTML进行内容展示,同时借助JavaScript增强交互性和动态效果。此外,现代Web开发还广泛使用各种框架和库,如Spring Boot、React和Vue.js,以提高开发效率和应用性能。 ... [详细]
  • PHP预处理常量详解:如何定义与使用常量 ... [详细]
  • 如何撰写适应变化的高效代码:策略与实践
    编写高质量且适应变化的代码是每位程序员的追求。优质代码的关键在于其可维护性和可扩展性。本文将从面向对象编程的角度出发,探讨实现这一目标的具体策略与实践方法,帮助开发者提升代码效率和灵活性。 ... [详细]
  • 在《Linux高性能服务器编程》一书中,第3.2节深入探讨了TCP报头的结构与功能。TCP报头是每个TCP数据段中不可或缺的部分,它不仅包含了源端口和目的端口的信息,还负责管理TCP连接的状态和控制。本节内容详尽地解析了TCP报头的各项字段及其作用,为读者提供了深入理解TCP协议的基础。 ... [详细]
  • 为了确保iOS应用能够安全地访问网站数据,本文介绍了如何在Nginx服务器上轻松配置CertBot以实现SSL证书的自动化管理。通过这一过程,可以确保应用始终使用HTTPS协议,从而提升数据传输的安全性和可靠性。文章详细阐述了配置步骤和常见问题的解决方法,帮助读者快速上手并成功部署SSL证书。 ... [详细]
  • 本文详细解析了 Android 系统启动过程中的核心文件 `init.c`,探讨了其在系统初始化阶段的关键作用。通过对 `init.c` 的源代码进行深入分析,揭示了其如何管理进程、解析配置文件以及执行系统启动脚本。此外,文章还介绍了 `init` 进程的生命周期及其与内核的交互方式,为开发者提供了深入了解 Android 启动机制的宝贵资料。 ... [详细]
  • ### 优化后的摘要本学习指南旨在帮助读者全面掌握 Bootstrap 前端框架的核心知识点与实战技巧。内容涵盖基础入门、核心功能和高级应用。第一章通过一个简单的“Hello World”示例,介绍 Bootstrap 的基本用法和快速上手方法。第二章深入探讨 Bootstrap 与 JSP 集成的细节,揭示两者结合的优势和应用场景。第三章则进一步讲解 Bootstrap 的高级特性,如响应式设计和组件定制,为开发者提供全方位的技术支持。 ... [详细]
  • 在Ubuntu系统中安装Android SDK的详细步骤及解决“Failed to fetch URL https://dlssl.google.com/”错误的方法
    在Ubuntu 11.10 x64系统中安装Android SDK的详细步骤,包括配置环境变量和解决“Failed to fetch URL https://dlssl.google.com/”错误的方法。本文详细介绍了如何在该系统上顺利安装并配置Android SDK,确保开发环境的稳定性和高效性。此外,还提供了解决网络连接问题的实用技巧,帮助用户克服常见的安装障碍。 ... [详细]
  • 在Java Web服务开发中,Apache CXF 和 Axis2 是两个广泛使用的框架。CXF 由于其与 Spring 框架的无缝集成能力,以及更简便的部署方式,成为了许多开发者的首选。本文将详细介绍如何使用 CXF 框架进行 Web 服务的开发,包括环境搭建、服务发布和客户端调用等关键步骤,为开发者提供一个全面的实践指南。 ... [详细]
  • 在Java项目中,当两个文件进行互相调用时出现了函数错误。具体问题出现在 `MainFrame.java` 文件中,该文件位于 `cn.javass.bookmgr` 包下,并且导入了 `java.awt.BorderLayout` 和 `java.awt.Event` 等相关类。为了确保项目的正常运行,请求提供专业的解决方案,以解决函数调用中的错误。建议从类路径、依赖关系和方法签名等方面入手,进行全面排查和调试。 ... [详细]
  • 本文以 www.域名.com 为例,详细介绍如何为每个注册用户提供独立的二级域名,如 abc.域名.com。实现这一功能的核心步骤包括:首先,确保域名支持泛解析,即将 A 记录设置为 *.域名.com,以便将所有二级域名请求指向同一服务器。接着,在服务器端使用 ASP.NET 2.0 进行配置,通过解析 HTTP 请求中的主机头信息,动态识别并处理不同的二级域名,从而实现个性化内容展示。此外,还需在数据库中维护用户与二级域名的对应关系,确保每个用户的二级域名都能正确映射到其专属内容。 ... [详细]
  • 在本地环境中部署了两个不同版本的 Flink 集群,分别为 1.9.1 和 1.9.2。近期在尝试启动 1.9.1 版本的 Flink 任务时,遇到了 TaskExecutor 启动失败的问题。尽管 TaskManager 日志显示正常,但任务仍无法成功启动。经过详细分析,发现该问题是由 Kafka 版本不兼容引起的。通过调整 Kafka 客户端配置并升级相关依赖,最终成功解决了这一故障。 ... [详细]
  • 开发日志:201521044091 《Java编程基础》第11周学习心得与总结
    开发日志:201521044091 《Java编程基础》第11周学习心得与总结 ... [详细]
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社区 版权所有