随着我对 OOP 的了解越来越多,并开始实现各种设计模式,我不断回到人们讨厌的案例 活动记录.

人们经常说它的扩展性不好(以 Twitter 为例)——但没有人真正解释 为什么 它的扩展性不好;和/或如何在没有缺点的情况下实现 AR 的优点(通过类似但不同的模式?)

希望这不会变成一场关于设计模式的圣战——我只想知道****具体**** Active Record 出了什么问题。

如果它不能很好地扩展,为什么不呢?

它还有哪些其他问题?

有帮助吗?

解决方案

ActiveRecord 设计模式ActiveRecord Rails ORM 库, ,而且还有大量 .NET 和其他语言的仿冒品。

这些都是不同的事情。他们大多遵循该设计模式,但以多种不同的方式扩展和修改它,因此在有人说“ActiveRecord 很糟糕”之前,需要通过说“哪个 ActiveRecord,有堆?”来限定它。

我只熟悉 Rails 的 ActiveRecord,我将尝试解决在使用它时提出的所有投诉。

@BlaM

我发现 Active Records 的问题是,它总是只有一张表

代码:

class Person
    belongs_to :company
end
people = Person.find(:all, :include => :company )

这会生成 SQL LEFT JOIN companies on companies.id = person.company_id, ,并自动生成关联的 Company 对象,以便您可以执行以下操作 people.first.company 并且它不需要访问数据库,因为数据已经存在。

@pix0r

Active Record 的固有问题是自动生成并执行数据库查询来填充对象和修改数据库记录

代码:

person = Person.find_by_sql("giant complicated sql query")

不鼓励这样做,因为它很难看,但对于您只是简单且只需要编写原始 SQL 的情况,这很容易完成。

@蒂姆·沙利文

...并且您选择模型的多个实例,您基本上是在执行“select * from ...”

代码:

people = Person.find(:all, :select=>'name, id')

这只会从数据库中选择名称和 ID 列,映射对象中的所有其他“属性”都将为零,除非您手动重新加载该对象,等等。

其他提示

我一直发现 ActiveRecord 非常适合基于 CRUD 的快速应用程序,其中模型相对平坦(例如,没有很多类层次结构)。然而,对于具有复杂 OO 层次结构的应用程序, 数据映射器 可能是一个更好的解决方案。虽然 ActiveRecord 假定表和数据对象之间的比例为 1:1,但对于更复杂的域,这种关系会变得难以处理。在他的 关于图案的书, ,Martin Fowler 指出 ActiveRecord 在模型相当复杂的情况下往往会崩溃,并建议 数据映射器 作为替代方案。

我在实践中发现这是真的。如果您的域中有大量继承,则将继承映射到 RDBMS 比映射关联或组合更困难。

我这样做的方法是拥有控制器通过这些 DataMapper(或“服务层”)类访问的“域”对象。它们并不直接镜像数据库,而是充当某些现实世界对象的 OO 表示。假设您的域中有一个 User 类,并且需要在检索该 User 对象时引用或加载其他对象的集合。数据可能来自许多不同的表,而 ActiveRecord 模式会让这变得非常困难。

例如,您的控制器代码不是直接加载 User 对象并使用 ActiveRecord 样式 API 访问数据,而是通过调用 UserMapper.getUser() 方法的 API 来检索 User 对象。该映射器负责从各自的表中加载任何关联的对象,并将完整的用户“域”对象返回给调用者。

本质上,您只是添加另一层抽象来使代码更易于管理。无论您的 DataMapper 类包含原始自定义 SQL,还是对数据抽象层 API 的调用,甚至访问 ActiveRecord 模式本身,对于接收良好的填充 User 对象的控制器代码来说并不重要。

无论如何,我就是这么做的。

我认为人们“讨厌”ActiveRecord 和 ActiveRecord 的“问题”之间可能存在一系列截然不同的原因。

在仇恨问题上,人们对任何与 Rails 相关的事物都充满敌意。就它的问题而言,它很可能就像所有技术一样,在某些情况下它是一个好的选择,在某些情况下它有更好的选择。根据我的经验,您无法利用 Rails ActiveRecord 的大部分功能的情况是数据库结构不良。如果您在没有主键的情况下访问数据,并且存在违反第一范式的数据,并且需要大量存储过程来访问数据,那么您最好使用 SQL 包装器。如果您的数据库结构相对良好,ActiveRecord 可以让您利用这一点。

添加到回复评论者的主题,这些评论者说 ActiveRecord 中的事情很难,并使用代码片段反驳

@Sam McAfee 假设您的域中有一个 User 类,并且需要在检索该 User 对象时引用或加载其他对象的集合。数据可能来自许多不同的表,而 ActiveRecord 模式会让这变得非常困难。

user = User.find(id, :include => ["posts", "comments"])
first_post = user.posts.first
first_comment = user.comments.first

通过使用 include 选项,ActiveRecord 允许您覆盖默认的延迟加载行为。

我的答案又长又晚,甚至不完整,但很好地解释了为什么我讨厌这种模式、观点甚至一些情绪:

1)简短版本:Active Record 创建一个“薄层“ 的 ”强结合力“在数据库和应用程序代码之间。这解决不了任何逻辑问题,任何问题,根本没有问题。恕我直言,它不提供任何价值,除了一些 句法糖 对于程序员(然后可以使用“对象语法”来访问关系数据库中存在的某些数据)。为程序员创造一些舒适感的努力应该(恕我直言......)更好地投资于低级数据库访问工具,例如simple、easy、plain 的一些变体 hash_map get_record( string id_value, string table_name, string id_column_name="id" ) 和类似的方法(当然,概念和优雅随着所使用的语言的不同而有很大差异)。

2)长版:在任何我对事物进行“概念控制”的数据库驱动项目中,我都避免使用 AR,这很好。我通常会构建一个 分层架构 (您迟早会将软件分层,至少在中型到大型项目中是这样):

A1) 数据库本身、表、关系,甚至一些逻辑(如果 DBMS 允许的话)(MySQL 现在也成熟了)

A2) 通常,不仅仅有数据存储:文件系统(数据库中的 blob 并不总是一个好的决定......),遗留系统(想象一下你自己“如何”访问它们,可能有很多种类......但这不是重点...)

B) 数据库访问层(在这一层,非常欢迎工具方法、帮助程序轻松访问数据库中的数据,但 AR 在这里不提供任何价值,除了一些语法糖)

C) 应用对象层:“应用程序对象”有时是数据库中表的简单行,但大多数时候它们是 化合物 无论如何,这些对象都附加了一些更高的逻辑,所以在这个级别的 AR 对象上投入时间显然是没有用的,浪费了宝贵的编码员时间,因为这些对象的“真正价值”、“更高的逻辑”需要实现无论如何,在 AR 对象之上 - 有或没有 AR!例如,为什么您想要“日志条目对象”的抽象?应用程序逻辑代码编写它们,但是是否应该能够更新或删除它们?听起来很傻,而且 App::Log("I am a log message") 是一些幅度更容易使用 le=new LogEntry(); le.time=now(); le.text="I am a log message"; le.Insert();. 。例如:在应用程序的日志视图中使用“日志条目对象”将适用于 100、1000 甚至 10000 条日志行,但迟早你必须进行优化 - 我敢打赌在大多数情况下,你只会使用那个小而漂亮的在你的应用程序逻辑中使用 SQL SELECT 语句(这完全打破了 AR 的想法……),而不是将这个小语句包装在严格固定的 AR 想法框架中,并用大量代码包装和隐藏它。您在编写和/或构建 AR 代码上浪费的时间本可以投资于一个更聪明的界面来读取日志条目列表(很多很多方式,没有限制)。编码员应该 敢于发明新的抽象概念 实现适合预期应用的应用逻辑,以及 不要愚蠢地重新实现愚蠢的模式, ,乍一看听起来不错!

D) 应用程序逻辑 - 实现交互对象以及创建、删除和列出(!)应用程序逻辑对象的逻辑(不,这些任务很少应该锚定在应用程序逻辑对象本身中:您办公桌上的那张纸是否告诉您办公室中所有其他纸张的名称和位置?忘记列出对象的“静态”方法,那是愚蠢的,是为了使人类思维方式适应[某些非全部 AR 框架之类]AR 思维而做出的糟糕妥协)

E) 用户界面 - 嗯,我在下面几行中写的内容非常非常主观,但根据我的经验,基于 AR 构建的项目经常忽略应用程序的 UI 部分 - 时间被浪费在创建晦涩的抽象上。最终,此类应用程序浪费了程序员的大量时间,并且感觉就像是程序员为程序员提供的应用程序,内部和外部都倾向于技术。编码员感觉很好(根据纸上的概念,辛苦的工作终于完成了,一切都完成并且正确......),而客户“只需要知道它需要像那样”,因为那是“专业”。好吧,抱歉,我离题了;-)

好吧,不可否认,这一切都是主观的,但这是我的经验(Ruby on Rails 除外,它可能有所不同,而且我对这种方法的实践经验为零)。

在付费项目中,我经常听到需要从创建一些“活动记录”对象作为更高级别应用程序逻辑的构建块开始。根据我的经验,这 经常明显地 这是某种借口,因为客户(大多数情况下是软件开发公司)没有一个好的概念,一个大视野,一个对产品最终应该是什么的概述。这些客户在僵化的框架中思考(“在十年前的项目中,它运作良好......”),他们可能充实实体,他们可能定义实体关系,他们可能打破数据关系并定义基本应用程序逻辑,但随后他们就停止了然后把它交给你,并认为这就是你所需要的......他们常常缺乏应用程序逻辑、用户界面、可用性等等的完整概念……他们缺乏大局观,缺乏对细节的热爱,他们希望你遵循 AR 的行事方式,因为……好吧,为什么,它在几年前的那个项目中起作用了,它让人们忙碌而沉默?我不知道。但“细节”将男人和男孩区分开来,或者......原来的广告语怎么样?;-)

多年后(十年的主动开发经验),每当客户提到“主动记录模式”时,我的警钟就响了。我学会了尝试去获得它们 回到那个重要的概念阶段, ,让他们三思而后行,尝试让他们展示自己概念上的弱点,或者如果他们缺乏辨别力,就完全避免它们(最后,你知道,一个还不知道自己想要什么的客户,甚至可能认为自己知道但实际上不知道) t,或者试图免费将概念工作具体化给我,花费了我许多宝贵的小时、天、周和月的时间,生命太短了......)。

所以,最后:这就是为什么我讨厌那种愚蠢的“活动记录模式”,并且我会尽可能避免它。

编辑: :我什至将其称为无模式。它不能解决任何问题(模式并不是为了创建语法糖)。它会产生很多问题:所有问题的根源(在这里的许多答案中都提到过……)是 它只是隐藏 接口背后的良好的、成熟的、强大的 SQL 受到模式定义的极大限制。

这种模式用语法糖取代了灵活性!

想一想,AR 为你解决了什么问题?

有些消息让我感到困惑。有些答案是“ORM”与“SQL”或类似的东西。

事实上,AR 只是一种简化的编程模式,您可以利用域对象来编写数据库访问代码。

这些对象通常具有业务属性(bean 的属性)和一些行为(通常作用于这些属性的方法)。

AR 只是说“向这些域对象添加一些方法”到数据库相关任务。

我不得不说,根据我的观点和经验,我不喜欢这种模式。

乍一看,这听起来不错。一些现代 Java 工具(例如 Spring Roo)使用这种模式。

对我来说,真正的问题只是 OOP 问题。AR 模式迫使您以某种方式添加从对象到基础设施对象的依赖关系。这些基础设施对象让领域对象通过 AR 建议的方法查询数据库。

我一直说两个层面是项目成功的关键。服务层(业务逻辑驻留在其中或可以通过某种远程技术导出,例如 Web 服务)和域层。在我看来,如果我们向域层对象添加一些依赖项(并非真正需要)来解析 AR 模式,我们的域对象将更难与其他层或(罕见的)外部应用程序共享。

Spring Roo 的 AR 实现很有趣,因为它不依赖于对象本身,而是依赖于一些 AspectJ 文件。但如果以后您不想使用 Roo 并且必须重构项目,AR 方法将直接在您的域对象中实现。

另一个角度来看。想象一下我们不使用关系数据库来存储我们的对象。例如,想象一下应用程序将我们的域对象存储在 NoSQL 数据库中或仅存储在 XML 文件中。我们会在域对象中实现执行这些任务的方法吗?我不这么认为(例如,在 XM 的情况下,我们会将与 XML 相关的依赖项添加到我们的域对象中......我认为这真的很悲伤)。那么为什么我们必须像 Ar 模式所说的那样在域对象中实现关系数据库方法呢?

总而言之,AR 模式听起来更简单,适合小型和简单的应用程序。但是,当我们拥有复杂且大型的应用程序时,我认为经典的分层架构是更好的方法。

问题是关于主动的记录设计模式。不是ORM工具。

最初的问题是用 Rails 标记的,并引用了用 Ruby on Rails 构建的 Twitter。Rails 中的ActiveRecord 框架是Fowler 的Active Record 设计模式的实现。

我看到的关于 Active Record 的投诉的主要内容是,当您围绕表创建模型并选择该模型的多个实例时,您基本上是在执行“select * from ...”。这对于编辑记录或显示记录来说很好,但是如果您想显示数据库中所有联系人的城市列表,您可以执行“从...选择城市”并仅获取城市。使用 Active Record 执行此操作需要选择所有列,但仅使用“城市”。

当然,不同的实现会以不同的方式处理这个问题。尽管如此,这是一个问题。

现在,您可以通过为您想要做的特定事情创建一个新模型来解决这个问题,但有些人会认为这付出的努力多于收益。

我,我喜欢 Active Record。:-)

华泰

我喜欢 SubSonic 只做一件事的方式。
任何一个

DataBaseTable.GetList(DataBaseTable.Columns.ColumnYouWant)

, , 或者:

Query q = DataBaseTable.CreateQuery()
               .WHERE(DataBaseTable.Columns.ColumnToFilterOn,value);
q.SelectList = DataBaseTable.Columns.ColumnYouWant;
q.Load();

但在延迟加载方面,Linq 仍然是王者。

@BlaM:有时我只是为连接结果实现了活动记录。并不总是必须是关系表 <--> Active Record。为什么不是“Join 语句的结果”<--> Active Record ?

我要讲的是Active Record作为一种设计模式,我还没见过ROR。

一些开发人员讨厌 Active Record,因为他们阅读了有关编写干净整洁的代码的智能书籍,这些书籍指出 Active Record 违反了单一责任原则,违反了域对象应该持久无知的 DDD 规则,以及此类书籍中的许多其他规则。

第二件事是 Active Record 中的域对象往往与数据库是一对一的,这可能被认为是某些类型系统(主要是 n 层)的限制。

这只是抽象的东西,我还没有看到 ruby​​ on Rails 实际实现了这种模式。

我发现 Active Records 的问题是,它总是大约 桌子。没关系,只要您确实只使用一张表,但是当您在大多数情况下处理数据时,您会在某处进行某种连接。

是的, 加入 通常比 根本没有加入 当谈到性能时,但是 加入 通常“假”加入 首先读取整个表A,然后使用获得的信息读取并过滤表B。

ActiveRecord 的问题在于它自动生成的查询可能会导致性能问题。

您最终会使用一些不直观的技巧来优化查询,这让您想知道首先手动编写查询是否会更有效。

尽管关于 SQL 优化的所有其他评论当然都是有效的,但我对活动记录模式的主要抱怨是它通常会导致 阻抗失配. 。我喜欢保持我的域干净并正确封装,而活动记录模式通常会破坏所有这样做的希望。

尝试建立多对多的多态关系。没那么容易。尤其是当您不使用性传播感染时。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top