这是一个理论问题,而不是实际问题。
我正在尝试设计一个遵循软件最佳实践的程序。但是我在寻找最好的方法来做某些事情时遇到了麻烦,这些事情可以进行测试驱动开发,良好的封装和抽象,同时还可以最小化耦合。
我计划有两个抽象层,一个数据库管理层和一个表管理层。
class TableManager()
{
init() {
manager = DatabaseManger()
}
do_operation(String operation) {
connection = manager.get_db_connection()
connection.execute(operation)
}
}
这似乎是一个好主意,因为实际的连接详细信息已从表管理器中隐藏了。但是这似乎也很糟糕,因为TableManager与DatabaseManager高度耦合,并且为了进行测试,我需要有一个单独的数据库连接来设置数据库状态,例如删除数据库和表之类,以便我们在数据库不存在时测试操作存在之类的东西
有人可以在这里权衡并挑战我的假设吗?我可能高估了某些因素的重要性,或者低估了其他因素。这是设计模式中的常见问题吗?
最佳答案
考虑将manager
作为构造函数参数传递给TableManager
,以便可以使用两种不同的数据库管理器实现:具体的实现和模拟的实现。
// These two classes have the same interface.
class ConcreteDBManager
{
auto numberOfTables()
{
auto connection = connectToRealDB();
return connection.queryAs<int>("COUNT TABLES");
}
};
class MockDBManager
{
auto numberOfTables()
{
return 5;
}
};
template <typename TDatabaseManager>
class TableManager
{
TDatabaseManager _manager;
TableManager(const TDatabaseManager& manager)
: _manager{manager}
{
}
auto numberOfTables()
{
return _manager.numberOfTables();
}
};
使用这种方法,您可以通过提供一个
TableManager
实例来测试MockDBManager
的功能,从而不必在单元测试中连接到实际的数据库...TEST(TableManager, RetrieveNumberOfTables)
{
MockDBManager mockDBManager;
TableManager<MockDBManager> tableManager(mockDBManager);
EXPECT_EQ(5, tableManager.numberOfTables());
}
...但是您可以使用相同的
TableManager
接口以生产代码连接到实际数据库:void realProductionCode()
{
ConcreteDBManager concreteDBManager;
TableManager<ConcreteDBManager> tableManager(concreteDBManager);
std::cout << tableManager.numberOfTables();
}