ActiveRecord模式的缺点是什么?


30

我很好奇将ActiveRecord模式用于数据访问/业务对象的缺点是什么。我能想到的唯一一个就是违反了单一责任原则,但是AR模式很常见,以至于仅凭这个原因似乎不足以证明不使用它(当然,视图可能会偏斜,因为我使用的代码通常都没有遵循任何 SOLID原则)。

我个人不是 ActiveRecord的粉丝(除了编写Ruby on Rails应用程序,AR感觉很“自然”),因为我觉得类做得太多,并且数据访问不应该取决于类本身处理。我更喜欢使用返回业务对象的存储库。我使用的大多数代码都倾向于使用ActiveRecord的变体,形式为(我不知道为什么该方法是布尔值):

public class Foo
{
    // properties...

    public Foo(int fooID)
    {
        this.fooID = fooID;
    }

    public bool Load()
    {
        // DB stuff here...
        // map DataReader to properties...

        bool returnCode = false;
        if (dr.HasRows)
            returnCode = true;

        return returnCode;
    }
}

或有时更“传统”的方式public static Foo FindFooByID(int fooID)为发现者提供一种方法,以及类似的东西public void Save()来保存/更新。

我得到的ActiveRecord通常要简单得多实施和使用,但它似乎有点简单了复杂的应用程序,你可以通过在库(封装数据访问逻辑有一个更强有力的架构更何况有很容易掉出来数据访问策略,例如,您可能使用存储的Procs +数据集并想要切换到LINQ或其他功能)

那么,在确定ActiveRecord是否是该职位的最佳候选人时,应考虑此模式的其他缺点吗?

Answers:


28

主要缺点是您的“实体”意识到自己的持久性,从而导致许多其他不良的设计决策。

另一个问题是,大多数活动记录工具箱基本上将1:1映射到具有零间接层的表字段。这在小范围内有效,但在您要解决棘手的问题时会分崩离析。


好吧,让对象知道其持久性意味着您需要执行以下操作:

  • 轻松地使数据库连接随处可用。这通常会导致讨厌的硬编码或某种形式的静态连接,无处不在。
  • 您的对象看起来比对象更像SQL。
  • 很难在断开连接的应用程序中执行任何操作,因为数据库是如此根深蒂固。

除此之外,还有很多其他错误的决定。


2
您能详细说明“其他不良的设计决策”吗?
凯文·克莱恩

2
谢谢。在Ruby on Rails开发中,我还没有发现这些问题。仍然可以分别测试行为和持久性。IMO将持久性与行为分离开来几乎没有实际价值。
凯文·克莱恩

@kevin:这些东西在诸如混合功能和鸭子输入之类的红宝石功能中没有什么弊端。对于静态语言(例如,OP在他的问题中使用的C#),将两者分开要困难一些。
Wyatt Barnett

@Wayne:对我来说,类只是将方法放入其中的盒子-我可以通过将业务逻辑与持久性放在单独的类中来将其与持久性分开,或者可以通过确保业务方法不执行I /从概念上将它们分开哦 在委派支持不佳的语言(例如Java)中,这样可以节省大量代码。太太,我刚进入休眠状态,所以我可能完全错了。
凯文·克莱恩

4
我要补充两点;1.与持久性机制的耦合使代码很难(即使不是不可能)正确地进行单元测试。2. Active Record通过集中的持久性机制将您的代码粘贴到关系上,如果您愿意的话,很难拆分全部组件。
istepaniuk

15

活动记录的最大缺点是您的域通常与特定的持久性机制紧密耦合。如果该机制需要全局更改,可能从基于文件的持久性更改为基于持久性的更改,或者在数据访问框架之间进行更改,则实现此模式的每个类都可能会更改。根据语言,框架和设计的不同,即使是简单的事情(例如更改数据库的位置或“所有者”),也可能需要遍历每个对象来更新数据访问方法(在大多数提供便捷访问的语言中,这并不常见。使用连接字符串配置文件)。

通常还需要您重复一遍。大多数持久性机制都有很多通用代码,可以连接到数据库并启动事务。DRY(不要重复自己)会告诉您作为编码器来集中这种逻辑。

这也使原子操作变得棘手。如果必须以全部或全部方式保存一组对象(例如发票及其InvoiceLines和/或客户和/或GL条目),则任一对象必须了解所有其他对象并控制其持久性(这扩大了控制对象的范围;大型的互连记录可以轻松地成为“神对象”,这些对象都知道有关其依赖项的所有信息),或者必须从域外部处理对整个事务的控制(在这种情况下,为什么要使用AR?)

从面向对象的角度来看,这也是“错误的”。在现实世界中,发票不知道如何归档自身,那么为什么发票代码对象知道如何将自身保存到数据库?当然,过度信奉“对象仅应建模其现实世界中的对象可以做什么”会导致贫血的领域模型(发票也不知道如何计算自己的总计,而是将总计计算拆分为另一个对象通常被认为是一个坏主意)。


在Ruby中,可以在非常简单的关键字或命令后定义或抽象持久性,这可能不是问题。在.NET中,需要更多的LoC来设置各种持久性机制,对于每个对象建立SQL连接通常是多余的,尤其是如果必须在一个原子事务中保存多个对象的情况下。无论您要保存发票还是客户,连接到数据库的外观都相同。您可以将通用内容抽象到代码库中所有ActiveRecord对象通用的基类中,或者将其提取到自己的类(存储库)中
KeithS 2011年

您能否提供Ruby ActiveRecord用法示例来说明您的观点?我没有遇到这些问题,但是我的应用程序很小。我可以用Ruby编写迁移,然后将其部署到不同的数据库实现中。我发现Ruby的ActiveRecord在消除重复方面非常有用,所以我不明白为什么您会断言使用ActiveRecord会产生相反的效果。保存没有问题:我只是修改了对象模型,然后让AR更新数据库以反映对象模型。
凯文·克莱恩

2

基本缺点是,它使您的域模型变得复杂,因为它不仅包含业务逻辑,而且还包含持久性信息。

因此解决方案是利用ORM 的Data Mapper实现。这将持久层分开了,我们现在更加关注实体业务逻辑。原则Data Mapper ORM。

但是这种方法也有一些复杂性,对于现在的查询您太过依赖Data Mapper了,使得查询面向环境。为简化起见,在域模型数据映射器之间引入了另一层称为存储库

存储库抽象出了持久层。从某种意义上讲,它使您可以感觉到面向对象的编程,它是所有相同类型对象的集合(就像存储在数据库表中的所有实体一样),您可以对它们执行操作,如collection操作,addremove包含

例如,对于User Entity,将存在UserRepository,表示可以执行操作的相同类型用户对象(存储在users表中)的集合。为了查询用户表,它使用了User Data Mapper,但它抽象为域模型User

存储库模式数据访问层的类型,另一种是数据访问对象的唯一区别存储库具有聚合根功能


存储库DAO模式不同,DAO是常规数据访问,存储库用于持久存储所有相同类型的对象。即所有存储库都应具有相同的接口(如数组或列表)。其他方法之王属于存储库。DAO级别较低,存储库可以使用DAO。通常,程序员(尤其是PHP)将存储库用作DAO,但这是不正确的。
xmedeko
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.