Java 中的面向数据编程 - 版本 1.1
Nicolai Parlog 于 2024 年 5 月 23 日近年来,Java 收到了许多新的语言特性,这些特性可以相互独立地使用,并且各自都有用:类型模式、switch 改进、记录和记录模式、密封类型以及其他一些模式。但正如偶尔发生的那样,整体在这里远远大于部分之和,当正确组合时,这些特性可以显著影响我们日常的编码。它们邀请我们从根本上扩展我们的设计模式库——在一个众所周知的方向上,但带有一个新的转折。因此,在本系列的六篇文章中(见本文末尾的详细列表),我们将探讨这种编程风格,并对 Brian Goetz 于 2022 年 6 月提出的指南进行一些小的更新。
面向对象编程
一切皆为对象。
面向对象编程(简称 OOP)可以归结为这句话。它表达了所有事物都可以(在 OOP 中)应该被建模为状态和行为的组合。实现它的最直接方法是创建将可变状态与作用于它们的方法相结合的类。这些类通常封装其状态,并且经常从接口继承其方法的契约,这些接口表示不同类在一种类型中的共同特征。
在 Java 中,这种方法无处不在,也许最明显的地方是集合 API。从 Iterable
到 Collection
和 List
,从 Queue
到 Set
,以及最近的 SequencedCollection
和 SequencedSet
,接口定义契约,而具体类,如 ArrayList
或 LinkedList
,HashSet
或 TreeSet
,PriorityQueue
或 ArrayDeque
,以各种方式实现它们,始终确保其可变状态保持隐藏,以便外部人员无法破坏它。
因此,我们经常以类似的方式设计自己的系统也就不足为奇了。在网上商店中,商品可能由 Item
接口建模,该接口由具体类实现,例如 Book
(带有 ISBN)、Furniture
(带有尺寸)和 ElectronicItem
(带有有关连接和电池电量的附加信息)。该接口具有诸如 addToCart
、purchase
、ship
或 reorder
之类的方法,并且可以通过实现新类轻松地将新的商品类型添加到系统中。
但是……情况往往并非如此简单。虽然将所有这些方法收集在 Item
上似乎很合理,因为它们都与购买流程交互,但添加 predictLowStock
(与基于机器学习的预订系统交互)、registerForRecommendations
(另一个 ML 系统,这次用于商品建议)和 reportPurchase
(注册可能危险商品的购买)让我们怀疑所有这些操作是否真的属于同一个接口。另一个问题是,目录表只能显示书籍,而 3D 户型规划器只能处理家具——Item
现在应该获得 tableOfContent
和 addToVirtualApartment
方法吗?每个方法在三个 Item
实现中的一个中包含有意义的行为,而在另外两个中则抛出异常或什么也不做?或者我们可以引入标志或进行 instanceof
检查,但这并不能解决一段时间后出现的另一个问题:所有这些子系统都共享商品实例,并且在更改其状态时反复踩踏彼此的脚趾,这会导致一些令人不快的错误。
不知何故,感觉我们美丽的设计被丑陋的现实粉碎了。一个主要原因是 OOP 最擅长对不断发展的过程进行建模,例如运输时间、库存管理或推荐系统,但不太适合对这些过程所操作的事物进行建模——例如上面的商品。那么,我们该怎么办呢?
面向数据编程
面向对象将世界视为相互作用对象的网络,每个对象都有一个内部的、通常是可变的状态(可能类似于自然生态系统),而面向数据编程(简称 DOP)则将其视为一系列系统,每个系统都有一个可能发生变化的状态,它们操作不可变数据(类似于生产线)。对不可变数据的操作?这听起来像是函数式编程(简称 FP),事实上 DOP 与 FP 有很多共同点。但 DOP 还包含可能可变的系统,这些系统可以用面向对象的方式建模。我们将在 本系列的最后一篇文章 中谈谈 DOP、FP 和 OOP 之间的关系。
面向数据编程基于许多原则,这些原则的具体表述尚未完全确定。在他在 “Java 中的面向数据编程” 中的开创性文章中,Oracle 的 Java 语言架构师 Brian Goetz 在 2022 年 6 月写道(略微重新排序)
- 数据是不可变的。
- 对数据进行建模,对整个数据进行建模,并且只对数据进行建模。
- 使非法状态不可表示。
- 在边界处进行验证。
这可以说是 1.0 版本。在各种项目中使用 DOP 大约 18 个月后(主要是演示和业余项目,但其中一个项目也已投入生产),我在这里提出了第一个修订版 1.1
- 以不可变且透明的方式对数据进行建模。
- 对数据进行建模,对整个数据进行建模,并且只对数据进行建模。
- 使非法状态不可表示。
- 将操作与数据分离。
文章系列
在接下来的几周内,我们将发布一篇关于这四个原则中的每一个的文章,并以第六篇文章结束本系列,该文章将面向数据编程置于面向对象和函数式编程的背景下,并提供一些关于何时何地使用它的指南。