简单学习Java API 设计实践

前言

了解在设计 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 文件)提供,不得跨多个实体进行拆分,因为该包的用户必须知道整个包的存在。

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

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

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

最小化包耦合

一个包中的类型可以引用其他包中的类型,例如,某个方法的参数类型和返回类型,以及某个字段的类型。这种包间耦合会在包上造成所谓的使用限制。这意味着 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 环境中使用。

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

(0)

相关推荐

  • JAVA演示阿里云图像识别API,印刷文字识别-营业执照识别

    最近有由于需要,我开始接触阿里云的云市场的印刷文字识别-营业执照识别这里我加上了官网的申请说明,只要你有阿里云账号就可以用,前500次是免费的,API说明很简陋,只能做个简单参考. 一.API介绍 JAVA示例: public static void main(String[] args) { String host = "https://dm-58.data.aliyun.com"; String path = "/rest/160601/ocr/ocr_business_

  • Java8中Lambda表达式使用和Stream API详解

    前言 Java8 的新特性:Lambda表达式.强大的 Stream API.全新时间日期 API.ConcurrentHashMap.MetaSpace.总得来说,Java8 的新特性使 Java 的运行速度更快.代码更少.便于并行.最大化减少空指针异常. 0x00. 前置数据 private List<People> peoples = null; @BeforeEach void before () { peoples = new ArrayList<>(); peoples

  • 使用JMX监控Zookeeper状态Java API

    一.背景 上一篇通过Java自带的JConsole来获取zookeeper状态.主要有几个不方便的地方,zk集群一般会部署3或者5台,在多个JConsole窗口中切换比较麻烦,各个zk服务及历史数据之间,不能直观比较.一般会做一个WEB管理页面来展示集群状态,设置报警阀值来做报警. 二.JVM平台提供Mbeans 在Java5.0以上版本,有一组API可以让Java应用程序和允许的工具监视和管理Java虚拟机(JVM)和虚拟机所在的本机操作系统.该组API在 java.lang.manageme

  • 浅析Java常用API(Scanner,Random)匿名对象

    API:即Application programming Interface,应用编程接口. Java中封装了许许多多的API供用户使用,Scanner与Random便是其中之一,API实际就是类,已经封装好了Scanner类,Random类,我们只需按照其语法编写即可,无需了解其根本源代码 Scanner类: 1.使用Scanner类需导入其所在包,import java.util.Scanner或import java.util.*(前者是导入util中的Scanner类,后者是导入util

  • java8使用Stream API方法总结

    Stream是java8中处理集合的关键抽象概念,它可以指定您希望对集合进行的操作,可以执行非常复杂的查找.过滤和映射数据等操作.使用Stream API对集合数据进行操作,就类似于使用SQL执行的数据库查询. Stream 的三个操作步骤 1.创建Stream. 得到Stream流的第一种方式: 可以通过Collection系列集合提供提供的Stream()或parallelStream @Test public void test1() { //可以通过Collection系列集合提供提供的

  • 深入了解Java 脚本化api编程

    Java脚本化API为谁准备? 脚本语言的一些有用的特性是: 方便:大多数脚本语言都是动态类型的.您通常可以创建新的变量,而不声明变量类型,并且您可以重用变量来存储不同类型的对象.此外,脚本语言往往会自动执行许多类型的转换,例如, 必要时 将数字10转换为"10". 开发快速原型:您可以避免编辑编译运行周期,只使用"编辑运行"! 应用扩展/定制:你可以"具体化"的部分应用程序,例如一些配置脚本,业务逻辑/规则和财务应用中的数学表达式 . 为应用添

  • 简单学习Java API 设计实践

    前言 了解在设计 Java API 时应该运用的一些 API 设计实践.这些实践通常很有用,而且可确保 API 能在诸如 OSGi 和 Java Platform Module System (JPMS) 之类的模块化环境中得到正确使用.有些实践是规定性的,有些则是禁止性的.当然,其他良好的 API 设计实践也同样适用. OSGi 环境提供了一个模块化运行时,使用 Java 类加载器概念来强制实施类型可见性封装.每个模块都将有自己的类加载器,该加载器将连接到其他模块的类加载器,以共享导出的包并使

  • 简单学习Java+MongoDB

    MongoDB一些概念 下面以MongoDB和MySql的对比来说明 MySQL MongoDB database(数据库) db(数据库) table(表) collection(集合) row(行记录) document(文档) column(列) field(字段) primary key(指定主键) "_id" : ObjectId("******")  自动生成内置主键 Mysql的数据形式如下 同样的数据在MongoDB下类似于JSON键值对,叫BSON

  • 简单学习Java抽象类要点及实例

    使用抽象类应该注意的几个要点: 包含一个或者多个抽象方法的类必须被声明为抽象类. 将类声明为抽象类,不一定含有抽象方法.通常认为,在抽象类中不应该包括具体方法,建议尽量将通用的域和方法放在超类中.抽象类不可以被实例化.即不能创建这个类的对象 实例代码: 复制代码 代码如下: import java.util.*; /** * This program demonstrates abstract classes. * @version 1.01 2004-02-21 * @author Cay H

  • 8个简单部分开启Java语言学习之路 附java学习书单

    之前为大家推荐了java语言阅读书籍,下面为大家介绍从哪几个方面开始学习java语言,具体内容如下 1. Java语言基础  谈到Java语言基础学习的书籍,大家肯定会推荐Bruce Eckel的<Thinking in Java>.它是一本写的相当深刻的技术书籍,Java语言基础部分基本没有其它任何一本书可以超越它.该书的作者Bruce Eckel在网络上被称为天才的投机者,作者的<Thinking in C++>在1995年曾获SoftwareDevelopment Jolt

  • Java API学习教程之正则表达式详解

    前言 正则表达式是什么应该不用过多介绍,每位程序员应该都知道,正则表达式描述的是一种规则,符合这种限定规则的字符串我们认为它某种满足条件的,是我们所需的.在正则表达式中,主要有两种字符,一种描述的是普通的字符,另一种描述的是元字符.其中元字符是整个正则表达式的核心,并由它完成规则的制定工作. 本篇文章主要从Java这门程序设计语言的角度理解正则表达式的应用,主要涉及以下内容: •基本正则表达式的理论基础 •Java中用于正则表达式匹配的类 •几种常用的正则表达式使用实例 一.正则表达式的理论基础

  • 10个微妙的Java编码最佳实践

    这是一个比Josh Bloch的Effective Java规则更精妙的10条Java编码实践的列表.和Josh Bloch的列表容易学习并且关注日常情况相比,这个列表将包含涉及API/SPI设计中不常见的情况,可能有很大影响. 我在编写和维护jOOQ(Java中内部DSL建模的SQL)时遇到过这些.作为一个内部DSL,jOOQ最大限度的挑战了Java的编译器和泛型,把泛型,可变参数和重载结合在一起,Josh Bloch可能不会推荐的这种太宽泛的API. 让我与你分享10个微妙的Java编码最佳

  • Java棋类游戏实践之单机版五子棋

    本文实例讲述了java实现的五子棋游戏代码,分享给大家供大家参考,具体代码如下 一.实践目标        1.掌握JavaGUI界面设计        2.掌握鼠标事件的监听(MouseListener,MouseMotionListener) 二.实践内容       设计一个简单的五子棋程序,能够实现五子棋下棋过程.如下图所示 1.五子棋棋盘类 package cn.edu.ouc.fiveChess; import java.awt.Color; import java.awt.Curs

  • 10种简单的Java性能优化

    最近"全网域(Web Scale)"一词被炒得火热,人们也正在通过扩展他们的应用程序架构来使他们的系统变得更加"全网域".但是究竟什么是全网域?或者说如何确保全网域? 扩展的不同方面 全网域被炒作的最多的是扩展负载(Scaling load),比如支持单个用户访问的系统也可以支持10 个.100个.甚至100万个用户访问.在理想情况下,我们的系统应该保持尽可能的"无状态化(stateless)".即使必须存在状态,也可以在网络的不同处理终端上转化

  • 一文学习Java NIO的ByteBuffer工作原理

    网络数据的基本单位永远是 byte(字节).Java NIO 提供 ByteBuffer 作为字节的容器,但该类过于复杂,有点难用. ByteBuf是Netty当中的最重要的工具类,它与JDK的ByteBuffer原理基本上相同,也分为堆内与堆外俩种类型,但是ByteBuf做了极大的优化,具有更简单的API,更多的工具方法和优秀的内存池设计. 1 API Netty 的数据处理 API 通过两个组件暴露--抽象类ByteBuf 和 接口 ByteBufHolder. ByteBuf API 的优

  • 5个Java API使用技巧

    本文介绍了一些关于Java API安全和性能方面的简单易用的技巧,其中包括保证API Key安全和开发Web Service方面中在框架方面选择的一些建议. 程序员都喜欢使用API!例如为app应用构建API或作为微服务架构体系的一部分.当然,使用API的前提是能让你的工作变得更轻松.为了简化开发和提高工作效率所作出的努力,有时也意味着需要寻找新的类库或者过程(或者减少过程).对于很多开发团队来说,对于其APP和API进行管理认证和访问控制要耗费很多的时间,因此我们需想分享一些技巧,它们能节约你

随机推荐