工厂设计模式是一种在软件开发中常用的设计模式,它提供了一种创建对象的最佳方式。在C++中,这种模式尤其有用,因为它可以帮助封装对象的创建过程,从而提高代码的可读性和可维护性。本文将探讨工厂设计模式的动机、两种实现方式,以及如何通过RAII(资源获取即初始化)和智能指针来简化代码和提高代码的可维护性。
工厂设计模式的主要动机是封装对象的创建过程。这样做的好处是多方面的:
首先,它有助于将对象的初始化代码与使用对象的代码分离,从而提高代码的可读性和可维护性。例如,如果第三方库的构造函数没有提供足够的对象初始化功能,可能需要额外的代码来进行构建和配置。通过在工厂类和方法中集中这些代码,可以轻松地管理和重用配置。如果第三方库随时间发展,只需要在工厂中维护代码,而不需要在代码库的多个实例中进行更新。
其次,工厂设计模式在解耦客户端代码和具体类方面发挥着关键作用。客户端代码不直接实例化类,而是依赖工厂来创建实例,这促进了松耦合。这种解耦允许在运行时进行多态行为,并且能够实现缓存机制或单例实例。它将使用创建的实例的代码与这些关注点隔离开来。
如果稍微扩展设计模式,工厂的抽象将有助于在测试中替换具体实现。这也是解耦的一个好处。
将检查工厂设计模式的两种实现方式。它们创建了一个SQLite数据库,其中包含一个表,并从中写入和读取数据。虽然它们可能作为设计模式使用的例子过于复杂,但这里的代码专注于在少量代码中提供实用的东西。
以下代码片段显示了第一种实现方式,这是一种传统的实现方式。工厂类持有数据库的标识,其工厂方法创建一个与之连接的数据库连接。
#include
#include
namespace sample {
class DbConnFactory {
private:
std::string mDbName;
public:
DbConnFactory(const std::string& dbName);
sqlite3 *open();
};
} // namespace sample
在这段代码中,定义了一个名为DbConnFactory的类,它有一个私有成员变量mDbName,用于存储数据库名称。公开的构造函数接受一个数据库名称,并将其存储在mDbName中。open()方法用于打开数据库并返回一个指向sqlite3结构的指针。
main函数使用工厂类创建数据库连接两次:一次用于数据库初始化,一次用于从中读取记录。
#include
#include
#include
#include
#include "DbConnFactory.hpp"
static const int LINE_MAX_LENGTH = 30;
void execIn(sqlite3* db, const std::string &sql, int (*callback)(void *notUsed, int argc, char **argv, char **colName) = nullptr) {
std::cout << "[SQL] " << sql << std::endl;
char *err = nullptr;
int rc = sqlite3_exec(db, sql.c_str(), callback, 0, &err);
if (rc != SQLITE_OK) {
std::string errMsg = "SQL Error (" + std::to_string(rc) + "): " + std::string(err);
sqlite3_free(err);
throw std::runtime_error(errMsg);
}
}
void createDb(sample::DbConnFactory& factory) {
sqlite3 *db = factory.open();
try {
execIn(db, "CREATE TABLE IF NOT EXISTS ORDER_ENTRY (ID INTEGER PRIMARY KEY AUTOINCREMENT, CUSTOMER TEXT NOT NULL);");
execIn(db, "INSERT INTO ORDER_ENTRY (CUSTOMER) VALUES ('Paul'); INSERT INTO ORDER_ENTRY (CUSTOMER) VALUES ('Mark');");
} catch (std::exception const &ex) {
sqlite3_close(db);
throw;
}
sqlite3_close(db);
}
void readDb(sample::DbConnFactory factory) {
sqlite3 *db = factory.open();
std::cout << std::string(20, '-') << std::endl;
execIn(db, "SELECT * FROM ORDER_ENTRY;", [](
void *notUsed, int argc, char **argv, char **colName) {
std::cout << std::string(20, '-') << std::endl;
for (int i = 0; i < argc; i++)
std::cout << colName[i] << ": " << (argv[i] ? argv[i] : "NULL") << std::endl;
return 0;
});
std::cout << std::string(20, '-') << std::endl;
}
int main(int argc, char *arg[]) {
sample::DbConnFactory factory("sample.db");
try {
createDb(factory);
readDb(factory);
} catch (std::exception const &ex) {
std::cerr << "[ERROR] " << ex.what() << std::endl;
}
}
这种实现方式的一个缺点是工厂方法返回一个指针。因此,调用者负责正确销毁返回的实例。在SQLite数据库连接的情况下,需要调用sqlite3_close函数,这在代码中多次出现。依赖调用者进行对象处理有点风险。在实践中,确保在所有调用者代码中实现这一点也是成本高昂的。
以下实现通过将对象销毁责任转移到工厂方法中,使用RAII和智能指针来改进代码。工厂方法创建一个数据库连接,并将其包装在一个类中,然后将其作为唯一指针返回。智能指针确保返回的实例将被销毁,包装类负责正确销毁数据库连接实例。
#ifndef H_SMART_DB_CONN_FACTORY
#define H_SMART_DB_CONN_FACTORY
#include
#include
#include
namespace sample {
class SmartDbConn {
private:
sqlite3* mDb;
public:
SmartDbConn(sqlite3* db);
~SmartDbConn();
sqlite3* db() { return mDb; }
};
class SmartDbConnFactory {
private:
std::string mDbName;
public:
SmartDbConnFactory(const std::string& dbName);
std::unique_ptr open();
};
} // namespace sample
#endif
在这段代码中,定义了两个类:SmartDbConn和SmartDbConnFactory。SmartDbConn类封装了一个sqlite3数据库连接,并在其析构函数中负责关闭数据库连接。SmartDbConnFactory类负责创建SmartDbConn实例,并将其作为唯一指针返回。
#include
#include
#include
#include
#include "SmartDbConnFactory.hpp"
static const int LINE_MAX_LENGTH = 30;
void execIn(sample::SmartDbConn &db, const std::string &sql, int (*callback)(void *notUsed, int argc, char **argv, char **colName) = nullptr) {
std::cout << "[SQL] " << sql << std::endl;
char *err = nullptr;
int rc = sqlite3_exec(db.db(), sql.c_str(), callback, 0, &err);
if (rc != SQLITE_OK) {
std::string errMsg = "SQL Error (" + std::to_string(rc) + "): " + std::string(err);
sqlite3_free(err);
throw std::runtime_error(errMsg);
}
}
void createDb(sample::SmartDbConnFactory& factory) {
auto db = factory.open();
execIn(*db, "CREATE TABLE IF NOT EXISTS ORDER_ENTRY (ID INTEGER PRIMARY KEY AUTOINCREMENT, CUSTOMER TEXT NOT NULL);");
execIn(*db, "INSERT INTO ORDER_ENTRY (CUSTOMER) VALUES ('Paul'); INSERT INTO ORDER_ENTRY (CUSTOMER) VALUES ('Mark');");
}
void readDb(sample::SmartDbConnFactory factory) {
auto db = factory.open();
std::cout << std::string(20, '=') << std::endl;
execIn(*db, "SELECT * FROM ORDER_ENTRY;", [](
void *notUsed, int argc, char **argv, char **colName) {
std::cout << std::string(20, '-') << std::endl;
for (int i = 0; i < argc; i++)
std::cout << colName[i] << ": " << (argv[i] ? argv[i] : "NULL") << std::endl;
return 0;
});
std::cout << std::string(20, '=') << std::endl;
}
int main(int argc, char *arg[]) {
sample::SmartDbConnFactory factory("sample.db");
try {
createDb(factory);
readDb(factory);
} catch (std::exception const &ex) {
std::cerr << "[ERROR] " << ex.what() << std::endl;
}
}