C++数据库非虚接口技术疑问:connect重载与成员变量放置
针对数据库非虚接口(NVI)实现的两个问题解答
问题1:如何在基类中重载connect函数,适配参数数量或类型不同的其他数据库?
在NVI模式下,基类的connect作为非虚公共接口,可以通过重载多个不同参数签名的connect函数,适配不同数据库的参数需求。每个重载的connect内部调用对应签名的私有纯虚函数,由子类根据自身数据库特性实现具体逻辑。
具体实现方式:
- 在基类
db_interface中新增多个public非虚connect函数,覆盖不同参数组合(比如简化版仅需库名和账号、带超时参数的版本等)。 - 对应新增同签名的
private纯虚函数,作为子类的具体实现入口。 - 子类按需实现对应虚函数,甚至可以在不同版本的虚函数间复用逻辑。
示例代码修改:
class db_interface : boost::noncopyable{ public: // 原全参数版本 void connect(std::string dbname, std::string host = "", std::string user = "", std::string password = "", std::string port ="") { if (connect_to_DB(dbname, host, user, password, port)) std::cout << dbname << ": connected\n"; else std::cout << dbname << ": connection failed\n"; } // 新增简化版本:仅需库名、账号、密码 void connect(std::string dbname, std::string user, std::string password) { if (connect_to_DB(dbname, user, password)) std::cout << dbname << ": connected\n"; else std::cout << dbname << ": connection failed\n"; } // 新增带超时参数的版本 void connect(std::string dbname, std::string host, std::string user, std::string password, int timeout) { if (connect_to_DB(dbname, host, user, password, timeout)) std::cout << dbname << ": connected\n"; else std::cout << dbname << ": connection failed\n"; } virtual ~db_interface(){}; private: virtual bool connect_to_DB(std::string dbname, std::string host, std::string user, std::string password, std::string port) = 0; virtual bool connect_to_DB(std::string dbname, std::string user, std::string password) = 0; virtual bool connect_to_DB(std::string dbname, std::string host, std::string user, std::string password, int timeout) = 0; }; // PostgreSQL子类实现对应虚函数 class postgreSQL : public db_interface{ private: PGconn *conn; bool connect_to_DB(std::string dbname, std::string host, std::string user, std::string password, std::string port) override { if (port.empty()) port = "5432"; conn = PQsetdbLogin(host.c_str(), port.c_str(), "", " ", dbname.c_str(), user.c_str(), password.c_str()); return PQstatus(conn) == CONNECTION_OK; } // 复用全参数版本逻辑 bool connect_to_DB(std::string dbname, std::string user, std::string password) override { return connect_to_DB(dbname, "", user, password, "5432"); } // 适配超时参数 bool connect_to_DB(std::string dbname, std::string host, std::string user, std::string password, int timeout) override { std::string conn_str = "host=" + host + " port=5432 dbname=" + dbname + " user=" + user + " password=" + password + " connect_timeout=" + std::to_string(timeout); conn = PQconnectdb(conn_str.c_str()); return PQstatus(conn) == CONNECTION_OK; } };
这种方式既保留了NVI模式的优势(公共接口统一,子类无法篡改流程逻辑),又能灵活适配不同数据库的参数差异。
问题2:是否可将*conn指针及后续新增的记录等数据置于基类中?
不建议直接将特定数据库的连接指针(如PGconn*)或记录对象放在基类,核心原因如下:
- 耦合性过高:基类作为抽象接口,应与具体数据库实现完全解耦。若在基类中放入
PGconn*,则基类必须引入PostgreSQL头文件,导致所有子类(包括其他数据库的子类)都被迫依赖PostgreSQL的库和头文件,违反依赖倒置原则。 - 类型不兼容:不同数据库的连接对象类型完全不同(比如MySQL是
MYSQL*,Oracle是OCIEnv*),基类无法用单一类型容纳所有数据库的连接对象。
如果需要在基层统一管理连接状态,可以采用抽象连接类的方式:
- 定义抽象的
db_connection基类,包含连接的通用操作接口(如disconnect、is_connected等)。 - 每个数据库子类实现对应的具体连接类(比如
pg_connection持有PGconn*,mysql_connection持有MYSQL*)。 - 在
db_interface基类中持有std::unique_ptr<db_connection>,由子类在连接成功后初始化对应的具体连接对象。
示例代码:
// 抽象连接类 class db_connection { public: virtual ~db_connection() = default; virtual bool is_connected() const = 0; virtual void disconnect() = 0; }; // PostgreSQL具体连接类 class pg_connection : public db_connection { private: PGconn* conn = nullptr; public: pg_connection(PGconn* c) : conn(c) {} ~pg_connection() override { if (conn) PQfinish(conn); } bool is_connected() const override { return conn && PQstatus(conn) == CONNECTION_OK; } void disconnect() override { if (conn) { PQfinish(conn); conn = nullptr; } } // 按需暴露原生连接指针 PGconn* get_raw_conn() { return conn; } }; class db_interface : boost::noncopyable{ public: void connect(std::string dbname, std::string host = "", std::string user = "", std::string password = "", std::string port ="") { conn_ptr = create_connection(dbname, host, user, password, port); if (conn_ptr && conn_ptr->is_connected()) std::cout << dbname << ": connected\n"; else std::cout << dbname << ": connection failed\n"; } virtual ~db_interface(){}; protected: virtual std::unique_ptr<db_connection> create_connection(std::string dbname, std::string host, std::string user, std::string password, std::string port) = 0; private: std::unique_ptr<db_connection> conn_ptr; }; class postgreSQL : public db_interface{ protected: std::unique_ptr<db_connection> create_connection(std::string dbname, std::string host, std::string user, std::string password, std::string port) override { if (port.empty()) port = "5432"; PGconn* conn = PQsetdbLogin(host.c_str(), port.c_str(), "", " ", dbname.c_str(), user.c_str(), password.c_str()); if (PQstatus(conn) == CONNECTION_OK) { return std::make_unique<pg_connection>(conn); } else { PQfinish(conn); return nullptr; } } };
这种方式既实现了连接的统一管理,又保持了基类与具体数据库的解耦,符合面向对象设计原则。
内容的提问来源于stack exchange,提问作者Namal
相关产品推荐
相关产品推荐

