gpt4 book ai didi

java - 数据访问层的设计模式

转载 作者:行者123 更新时间:2023-12-01 21:40:02 25 4
gpt4 key购买 nike

我有一个使用数据库 (MongoDB) 来存储信息的应用程序。过去,我使用了一个充满静态方法的类来保存和检索数据,但后来我意识到这不是非常面向对象的,也不是面向 future 的。

尽管我不太可能更改数据库,但我更愿意使用不会将我与 Mongo 紧密联系在一起的东西。我还希望能够使用从数据库刷新缓存对象的选项来缓存结果,但这不是必需的,可以在另一个地方完成。

我查看了数据访问对象,但它们似乎没有很好地定义,而且我找不到任何很好的实现示例(在 Java 或类似语言中)。我也有许多一次性案例,例如为选项卡完成查找用户名,这似乎不太合适,并且会使 DAO 变得庞大而臃肿。

是否有任何设计模式可以促进获取和保存对象而不会过于特定于数据库?好的实现示例会有所帮助(最好是在 Java 中)。

最佳答案

好吧,正如您所指出的,Java 中数据存储的常见方法根本不是面向对象的。这本身既不好也不好:“面向对象”既不是优点也不是缺点,它只是众多范式之一,有时有助于良好的架构设计(有时则没有)。

Java 中的 DAO 通常不是面向对象的原因正是您想要实现的 - 放松对特定于数据库的依赖。在设计更好的语言中,它允许多重继承,这当然可以以面向对象的方式非常优雅地完成,但是对于 java,它似乎比它值得的麻烦更多。

从更广泛的意义上讲,非 OO 方法有助于将应用程序级数据与其存储方式分离。这不仅仅是(非)依赖于特定数据库的细节,而且还依赖于存储模式,这在使用关系数据库时尤其重要(不要让我开始使用 ORM):您可以拥有一个精心设计的关系模式由您的 DAO 无缝转换为应用程序 OO 模型。

所以,现在java中的大多数DAO本质上都是你在开始时提到的——类,充满了静态方法。一个区别是,与其将所有方法都设为静态,不如拥有一个静态“工厂方法”(可能在不同的类中),它返回 DAO 的(单例)实例,该实例实现特定接口(interface),由应用程序代码用于访问数据库:

public interface GreatDAO {
User getUser(int id);
void saveUser(User u);
}
public class TheGreatestDAO implements GreatDAO {
protected TheGeatestDAO(){}
...
}
public class GreatDAOFactory {
private static GreatDAO dao = null;
protected static synchronized GreatDao setDAO(GreatDAO d) {
GreatDAO old = dao;
dao = d;
return old;
}
public static synchronized GreatDAO getDAO() {
return dao == null ? dao = new TheGreatestDAO() : dao;
}
}

public class App {
void setUserName(int id, String name) {
GreatDAO dao = GreatDAOFactory.getDao();
User u = dao.getUser(id);
u.setName(name);
dao.saveUser(u);
}
}

为什么要这样做而不是静态方法?那么,如果您决定切换到不同的数据库怎么办?自然地,您将创建一个新的 DAO 类,为您的新存储实现逻辑。如果您使用的是静态方法,您现在必须检查所有代码,访问 DAO,并将其更改为使用您的新类,对吗?这可能是一个巨大的痛苦。如果您改变主意并想切换回旧数据库怎么办?

使用这种方法,您需要做的就是更改 GreatDAOFactory.getDAO()并让它创建一个不同类的实例,你的所有应用程序代码都将使用新的数据库而不做任何更改。

在现实生活中,这通常不需要对代码进行任何更改即可完成:工厂方法通过属性设置获取实现类名称,并使用反射对其进行实例化,因此,切换实现所需要做的就是编辑属性文件。实际上有框架 - 比如 springguice - 为您管理这种“依赖注入(inject)”机制,但我不会详细介绍,首先,因为它确实超出了您的问题范围,而且,因为我不一定相信您从使用中获得的好处对于大多数应用程序,这些框架值得与它们集成。

与静态相反,这种“工厂方法”的另一个(可能更可能被利用)的好处是可测试性。想象一下,你正在编写一个单元测试,它应该测试你 App 的逻辑。类独立于任何底层 DAO。您不希望它使用任何真正的底层存储,原因有几个(速度、必须设置它并清理后记、可能与其他测试发生冲突、DAO 中的问题可能会污染测试结果,与 App 无关) ,实际上正在测试等)。

为此,您需要一个测试框架,例如 Mockito ,这允许您“模拟”任何对象或方法的功能,将其替换为具有预定义行为的“虚拟”对象(我将跳过细节,因为这又超出了范围)。所以,你可以创建这个虚拟对象来代替你的 DAO,并制作 GreatDAOFactory通过调用 GreatDAOFactory.setDAO(dao) 返回您的假人而不是真实的东西在测试之前(并在测试之后恢复它)。如果您使用静态方法而不是实例类,这是不可能的。

还有一个好处,有点类似于我上面描述的切换数据库,就是用附加功能“拉长”你的 dao。假设您的应用程序随着数据库中数据量的增长而变慢,并且您决定需要一个缓存层。实现一个包装类,它使用真实的 dao 实例(作为构造函数参数提供给它)访问数据库,并将它读取的对象缓存在内存中,以便它们可以更快地返回。然后您可以制作您的 GreatDAOFactory.getDAO实例化这个包装器,以便应用程序利用它。

(这被称为“委托(delegate)模式”......看起来很痛苦,特别是当你在 DAO 中定义了很多方法时:你必须在包装器中实现所有这些,甚至只改变一个的行为. 或者,您可以简单地对您的 dao 进行子类化,并以这种方式为其添加缓存。这将减少前期编码的无聊程度,但是当您决定更改数据库时可能会出现问题,或者更糟糕的是,可以选择来回切换实现。)

“工厂”方法的一种同样广泛使用(但在我看来较差)的替代方法是制作 dao所有需要它的类中的成员变量:
public class App {
GreatDao dao;
public App(GreatDao d) { dao = d; }
}

这样,实例化这些类的代码需要实例化 dao 对象(仍然可以使用工厂),并将其作为构造函数参数提供。我上面提到的依赖注入(inject)框架,通常会做类似的事情。

这提供了我之前描述的“工厂方法”方法的所有好处,但是,就像我说的那样,在我看来并没有那么好。这里的缺点是必须为每个应用程序类编写一个构造函数,一遍又一遍地做同样的事情,并且在需要时也不能轻松地实例化这些类,并且失去了一些可读性:代码库足够大,你的代码的读者,不熟悉它,将很难理解使用了 dao 的哪个实际实现,它是如何实例化的,它是单例,线程安全的实现,它是保持状态还是缓存任何事情,如何做出选择特定实现的决定等。

关于java - 数据访问层的设计模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27723477/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com