设计模式——数据访问对象模式
定义与概念
- 数据访问对象(Data Access Object,DAO)模式是一种用于分离数据访问逻辑和业务逻辑的设计模式。它提供了一个抽象的接口,用于对数据源(如数据库、文件系统等)进行访问,使得业务逻辑层不需要直接处理底层数据存储的细节,从而提高了代码的可维护性和可扩展性。
- 例如,在一个应用程序中,如果要从数据库中读取和写入用户信息,使用 DAO 模式可以创建一个用户数据访问对象,该对象封装了所有与数据库交互的操作(如查询用户、插入用户、更新用户信息等)。业务逻辑层(如用户管理模块)只需调用这个数据访问对象的方法,而不用关心数据库的具体类型(是关系型数据库还是非关系型数据库)、连接方式以及 SQL 语句的编写等细节。
结构组成
- 数据访问对象接口(DAO Interface):
它定义了访问数据的抽象方法,这些方法是业务逻辑层与数据访问层之间的契约。例如,对于用户数据访问对象,接口可能包括getUserById(int id)、addUser(User user)、updateUser(User user)等方法。这个接口使得业务逻辑层可以以一种统一的方式与不同的数据访问实现进行交互。 - 具体数据访问对象(Concrete DAO):
实现了数据访问对象接口,负责与实际的数据源进行交互。它包含了具体的数据访问逻辑,如数据库连接的建立、SQL 语句的执行等。以关系型数据库为例,具体的数据访问对象可能会使用数据库连接库(如 ODBC、JDBC 或 C++ 的数据库连接库)来执行 SQL 查询,以获取或修改数据。 - 值对象(Value Object):
也称为数据传输对象(Data Transfer Object,DTO),它是一种简单的数据结构,用于在不同层之间传递数据。在 DAO 模式中,值对象通常用于封装从数据源获取的数据或者要写入数据源的数据。例如,用户值对象可能包含用户的姓名、年龄、邮箱等属性,数据访问对象的方法通常会以值对象作为参数或者返回值。 - 数据源(Data Source):
这是实际存储数据的地方,如数据库、文件、网络服务等。数据访问对象与数据源进行交互,以实现数据的读取和写入操作。例如,在一个基于数据库的应用程序中,数据源就是数据库管理系统,数据访问对象通过数据库驱动程序与数据库进行连接并操作数据。
工作原理
- 业务逻辑层需要访问数据时,它通过数据访问对象接口调用相应的方法。具体的数据访问对象接收到请求后,与数据源建立连接(如果尚未连接),然后根据请求的类型(如查询、插入、更新等)执行相应的操作。在操作过程中,可能会涉及将数据源中的数据转换为值对象,或者将值对象中的数据转换为适合数据源存储的形式。操作完成后,数据访问对象将结果(可能是一个或多个值对象,或者是表示操作成功 / 失败的状态信息)返回给业务逻辑层。
- 例如,在一个学生成绩管理系统中,业务逻辑层需要获取某个学生的成绩信息。它调用学生成绩数据访问对象接口的getStudentGrades(int studentId)方法。具体的数据访问对象(假设使用关系型数据库)首先连接到数据库,然后执行 SQL 查询语句来获取该学生的成绩数据,将数据库中的数据行转换为学生成绩值对象,最后将值对象返回给业务逻辑层。
代码示例
以下是一个简单的 C++ 数据访问对象模式示例,用于访问一个简单的用户信息数据库(这里为了简化,假设数据存储在一个std::vector中,实际应用中可能是真正的数据库)。
- 值对象 - 用户(User)
class User {
public:
int id;
std::string name;
std::string email;
};
- 数据访问对象接口 - 用户数据访问接口(UserDAOInterface)
class UserDAOInterface {
public:
virtual User getUserById(int id) = 0;
virtual void addUser(User user) = 0;
virtual void updateUser(User user) = 0;
};
- 具体数据访问对象 - 用户数据访问实现(UserDAOImpl)
class UserDAOImpl : public UserDAOInterface {
private:
std::vector<User> users;
public:
User getUserById(int id) override {
for (const User& user : users) {
if (user.id == id) {
return user;
}
}
// 如果未找到用户,返回一个空用户对象(这里可以根据实际情况处理)
User emptyUser;
return emptyUser;
}
void addUser(User user) override {
users.push_back(user);
}
void updateUser(User user) override {
for (User& u : users) {
if (u.id == user.id) {
u.name = user.name;
u.email = user.email;
break;
}
}
}
};
- 业务逻辑层使用示例(假设这是一个用户管理模块)
class UserManager {
private:
UserDAOInterface* userDAO;
public:
UserManager(UserDAOInterface* dao) : userDAO(dao) {}
User getUser(int id) {
return userDAO->getUserById(id);
}
void addNewUser(User user) {
userDAO->addUser(user);
}
void updateUserInfo(User user) {
userDAO->updateUser(user);
}
};
- 客户端使用示例
int main() {
UserDAOImpl userDAO;
UserManager manager(&userDAO);
User newUser;
newUser.id = 1;
newUser.name = "张三";
newUser.email = "zhangsan@example.com";
manager.addNewUser(newUser);
User retrievedUser = manager.getUser(1);
std::cout << "获取用户姓名: " << retrievedUser.name << ", 邮箱: " << retrievedUser.email << std::endl;
return 0;
}
优点
- 分离关注点:
将数据访问逻辑与业务逻辑分离,使得业务逻辑层可以专注于业务规则的实现,而数据访问层专注于数据的存储和读取。这样可以提高代码的可读性和可维护性,例如,在一个大型的企业级应用中,开发人员可以分别负责业务逻辑和数据访问逻辑,降低了代码的耦合度。 - 可替换数据源:
由于业务逻辑层通过数据访问对象接口与数据访问层交互,因此可以很容易地替换数据源而不影响业务逻辑层。例如,如果从关系型数据库切换到非关系型数据库,只需要实现一个新的具体数据访问对象,而业务逻辑层的代码基本不变。 - 便于单元测试:
业务逻辑层可以独立于数据源进行单元测试。开发人员可以使用模拟的数据访问对象来提供测试数据,从而更容易地验证业务逻辑的正确性。例如,在测试用户管理模块时,可以创建一个模拟的用户数据访问对象,返回预设的用户数据,以测试用户管理模块的各种功能。
缺点
- 增加代码复杂度:
引入数据访问对象模式会增加系统的层次结构和代码量。对于简单的应用程序,可能会显得过于复杂。例如,在一个小型的命令行工具中,如果只有少量的数据访问操作,使用 DAO 模式可能会增加不必要的代码开销。 - 性能问题可能被掩盖:
在数据访问对象的抽象过程中,可能会忽略一些底层数据源的性能优化点。例如,在数据库访问中,如果不考虑数据库连接池、查询优化等性能相关的因素,可能会导致系统性能下降。同时,数据访问对象的多层调用也可能会引入一定的性能开销。
原文地址:https://blog.csdn.net/chuliling0446/article/details/144009288
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!