史上最愚蠢的持久化文章。

发布者    |      

不知何故这篇文章的愚蠢被 InfoQ 链接。这篇帖子实在不值得花时间去反驳,但我无聊,所以我还是会继续。

Active Record 是一种众所周知的数据持久化模式。它已被 Rails、Hibernate 和许多其他 ORM 工具采用。

实际上,Hibernate 并未实现 Active Record 模式。如果你真的觉得需要根据 Fowler 的分类法对其进行分类,Hibernate 是一种 数据映射器

表和类、列和字段之间存在 1:1 的对应关系。(或者几乎是如此)。正是这种 1:1 的对应关系让我感到烦恼。实际上,所有 ORM 工具都让我感到烦恼。为什么?因为这个映射假设表和对象是同构的。

好吧,这可能是 Active Record 的情况,但 Hibernate 和大多数其他 ORM 解决方案在这方面要灵活得多。实际上,任何 ORM 解决方案的主要目标之一就是允许对象和表在合理范围内不进行同构。

从面向对象一开始,我们就知道对象中的数据应该是隐藏的,而公共接口应该是方法。换句话说:对象导出行为,而不是数据。对象有隐藏的数据和公开的行为。

一个奇怪的说法。对象是状态化的,对象的方法的主要目的之一就是将这种状态(数据)导出到世界。

另一方面,数据结构有公开的数据,没有行为。在 C++ 和 C# 这样的语言中,struct关键字用于描述具有公共字段的公共数据结构。如果有任何方法,它们通常是导航性的。它们不包含业务规则。

我不相信“数据结构”这个词本身有任何关于“行为”的含义。当然,它并不暗示“没有行为”。

因此,数据结构和对象是完全相反的。它们是虚拟的相反面。一个暴露行为并隐藏数据,另一个暴露数据而没有行为。但它们之间的对立不止于此。

哇!完全相反!真的吗?

相反,我知道的大多数人会将对象视为

  1. 一个数据结构的集合,以及
  2. 在这个数据结构上操作的功能。

数据是对象的一个内在部分;不是它的完全相反

处理对象的算法有这样一个优势,即不需要知道它们正在处理的对象类型。

这不是真的。

老例子:shape.draw();说明了这一点。调用者根本不知道正在绘制的是什么形状。

正确;老例子完美地说明了这一点:调用者非常确切地知道它正在绘制的是形状.

使用对象的算法对添加新的类型免疫...对象对添加新的功能不是免疫的...使用数据结构的算法对添加新的功能免疫...使用数据结构的算法对添加新的类型不是免疫的...系统可能需要添加新类型的部分,应围绕对象进行设计。另一方面,任何可能需要新功能的系统部分应围绕数据结构进行设计。

这都是在对象数据结构之间的错误二分法的基础上。实际上,在任何特定的系统部分中,我们都没有必要倾向于一种观点或另一种观点。

相反,我们在设计对象模型时利用子类型(继承/多态)。通过仔细设计类型层次结构,我们允许系统适应新类型和新行为的引入。

再次注意几乎完全相反的对立。对象和数据结构传达了近似的免疫和脆弱性。

反复断言错误二分法并不能使其成为真理。

我对Active Record的问题在于它对这两种截然不同的编程风格产生了混淆。

只有一种编程风格。

数据库表是一个数据结构。它暴露了数据而没有行为。

正确。

但Active Record看起来像一个对象。

正确。ActiveRecord 确实是一个对象。

它有“隐藏的数据”,并暴露了行为。我把“隐藏”这个词放在引号里,因为实际上数据并不是隐藏的。几乎所有ActiveRecord的衍生品都通过访问器和修改器导出数据库列。

这里似乎有一个深层次的混淆,即究竟什么是应该被隐藏的。不是状态,也不是数据必须隐藏,而是实现细节。这完全不同。

确实,Hibernate这样的产品绝对不需要通过访问器和修改器导出数据库列。持久属性如果适当的话可以是私有的。状态(数据)可以根据客户端代码的需求隐藏或暴露。

确实,Active Record旨在像数据结构一样使用。另一方面,许多人将业务规则方法放在他们的Active Record类中;这使得它们看起来像对象。

又是错误二分法。

这导致了一个困境。Active Record真的在哪一边?它是对象吗?还是数据结构?

它既是。没有界限。

这个困境是人们经常引用的关系数据库和面向对象语言之间阻抗不匹配的基础。表是数据结构,而不是类。对象是封装的行为,而不是数据库行。

阻抗不匹配是两种范式可用的不同建模结构之间的。

问题是活动记录是数据结构。将业务规则方法放入其中并不能将其变成真正的对象。

相反,数据结构加上业务规则方法正是定义一个对象的内容。

最终,使用活动记录的算法容易受到模式变化和类型变化的影响。它们不像使用对象的算法那样免受类型变化的影响。

任何面向对象的代码都容易受到类型系统变化的影响。声称使用对象的算法类型变化免疫是极其荒谬的。

你可以通过意识到在关系数据库中实现多态层次结构是多么困难来证明这一点。当然,这是可能的,但每一种实现方法都是一种黑客技术。最终结果是,很少有数据库模式,因此很少使用活动记录,采用能够传递对类型变化免疫的多态性。

胡说。传统的数据建模有强大的、优雅的方法来建模子类型,现代ORM解决方案如Hibernate可以轻松地在关系和面向对象的子类型方法之间进行映射。

因此,围绕活动记录构建的应用程序是围绕数据结构构建的应用程序。而围绕数据结构构建的应用程序是程序性的——它们不是面向对象的。我们围绕活动记录构建应用程序时错过的机会是使用面向对象设计的机会。

持久类体现继承关系和子类型。有很多机会可以利用面向对象设计原则。如果你的ORM解决方案不支持继承,获取一个支持继承的

应用程序应该围绕对象进行设计和构建。这些对象应该暴露业务行为,并隐藏任何数据库痕迹。我们拥有Employee表在数据库中,并不意味着我们必须在应用程序中拥有Employee类。

我们在业务领域中有员工,并在我们的业务领域模型中有一个概念,这意味着我们几乎肯定会在面向对象和关系实现的模型中有EmployeeEMPLOYEE表和类。Employee我们可能有一些在数据库接口层持有

Employee的Active Records,但到信息到达应用程序时,它可能已经在非常不同类型的对象中。

这是极其不可能的。关系模式和对象模型都是表示存在于业务领域的同一实体的表示。这些实体存在于应用程序的所有层中,从用户界面到数据库。

我并不是反对使用活动记录。正如我在本博客的第一部分所说,我认为这个模式非常有用。我所倡导的是应用程序和活动记录之间的分离。活动记录属于分离数据库和应用程序的层。它在数据库的硬数据结构和应用程序中暴露行为的对象之间提供了一个非常方便的中转站。

哦,太棒了。这一切都只是更多无用的的借口?请,已经足够多了!

所以,最终,我并不反对使用Active Record。我只是不想让Active Record成为应用程序的组织原则。它是一个很好的数据库与应用程序之间的传输机制;但是我不希望应用程序了解Active Record。我想让应用程序围绕暴露行为并隐藏数据的对象进行构建。我通常希望应用程序对类型变化具有免疫力;并且我想构建应用程序,以便可以通过添加新类型来添加新功能。

应用程序应该围绕业务领域模型进行定位。对象是实现细节。


回到顶部