java api 设计_Java API设计实践

使你的API在模块化和非模块化Java环境中都可用

在优锐课的java学习分享中,对微服务有了更深层次的新概念。关于API设计实践一点就通了。

介绍

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

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

Java 9引入了JPMS,它使用Java语言规范中的访问控制概念提供了一个模块化平台,以强制执行类型可访问性封装。每个模块定义导出哪些软件包,以便其他模块访问。默认情况下,JMPS层中的模块都驻留在同一类加载器中。

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

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

Java软件包必须是内聚的,稳定的单元

必须设计Java软件包以确保它是一个内聚且稳定的单元。在模块化Java中,包是模块之间的共享实体。一个模块可以导出软件包,以便其他模块可以使用该软件包。因为包是模块之间共享的单元,所以包必须具有凝聚力,因为包中的所有类型都必须与包的特定用途相关。不建议使用诸如java.util之类的Grab bag软件包,因为此类软件包中的类型通常彼此无关。由于软件包的不相关部分引用了其他不相关的软件包,因此此类非粘性软件包可能会导致大量依赖关系,并且对软件包一个方面的更改会影响所有依赖于软件包的模块,即使某个模块可能实际上并未使用该软件包的一部分。软件包已被修改。

由于包是单位共享的,因此包的内容必须是众所周知的,并且随着包在未来版本中的发展,所包含的API仅会以兼容的方式更改。这意味着程序包一定不能支持API超集或子集。例如,请参阅javax.transaction作为其内容不稳定的软件包。包的用户必须能够知道包中可用的类型。这也意味着软件包应该由单个实体(例如jar文件)交付,而不应拆分为多个实体,因为软件包的用户必须知道整个软件包都存在。

此外,该软件包必须以兼容的方式在将来的版本中发展。因此,应该对软件包进行版本控制,并且其版本号必须根据语义版本控制的规则进行发展。还有一份关于语义版本控制的OSGi白皮书。

但是最近我意识到对于软件包的主要版本更改的语义版本建议是错误的。包的演变必须是功能的积累。在语义版本控制中,这会增加次要版本。删除功能后,即对程序包进行了不兼容的更改,而不是增加主版本,必须移至新的程序包名称,以使原始程序包仍然兼容。要了解为什么这是重要和必要的,请参阅这篇关于Go的语义导入版本控制的文章以及Rich Richicic在Clojure / conj 2016上的精彩主题演讲。这两种情况都使得我们有必要改用新的软件包名称而不是更改主要软件包名称。对软件包进行不兼容更改时的版本。

最小化封装耦合

包中的类型可以引用其他包中的类型。 例如,方法的参数类型和返回类型以及字段的类型。 这种包装间的耦合会在包装上产生所谓的使用限制。 这意味着API使用者必须使用与API提供程序相同的引用包,以使它们都能理解引用的类型。

通常,我们要最小化此包装耦合,以最小化包装上的使用限制。 这简化了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方法或静态字段。 当需要单例对象时,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声明式服务规范定义了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类型由诸如servlet容器之类的API提供程序实现。向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联盟定义了文档注释,ProviderType和ConsumerType以标记API包中类型的角色。这些注释位于osgi.annotation jar中,可在你的API中使用。

结论

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

> 喜欢这篇文章的可以点个赞,欢迎大家留言评论,记得关注我,每天持续更新技术干货、职场趣事、海量面试资料等等

> 如果你对java技术很感兴趣也可以加裙  907135806交流技术学习,共同进步。

> 不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代

文章写道这里,欢迎完善交流。最后奉上近期整理出来的一套完整的java架构思维导图,分享给大家对照知识点参考学习。有更多JVM、Mysql、Tomcat、Spring Boot、Spring Cloud、Zookeeper、Kafka、RabbitMQ、RockerMQ、Redis、ELK、Git等Java干货3e31144aa1f7ea20699ff1526d72b66b.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值