这是一个理论问题而不是实际问题。
我正在尝试设计一个遵循软件最佳实践的程序。但是我无法找到最好的方法来做一些允许测试驱动开发和良好的封装和抽象的事情,同时最大限度地减少耦合。
我计划有两层抽象,一个数据库管理层和一个表管理层。
class TableManager()
{
init() {
manager = DatabaseManger()
}
do_operation(String operation) {
connection = manager.get_db_connection()
connection.execute(operation)
}
}
这似乎是一个好主意,因为实际的连接细节是从表管理器隐藏的。但它似乎也很糟糕,因为TableManager高度耦合到DatabaseManager并且为了测试,我需要有一个单独的数据库连接来设置数据库状态,比如删除数据库和表,所以我们测试操作时数据库没有存在和类似的东西
有人可以在这里权衡并挑战我的假设吗?我可能会过高估计某些因素的重要性或低估其他因素。这是设计模式的常见问题吗?
答案 0 :(得分:3)
考虑将manager
作为构造函数参数传递给TableManager
,这样就可以有两个不同的数据库管理器实现:具体的和模拟的。< / p>
// 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
的功能,这样您就不必在单元测试中连接到实际的数据库... < / p>
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();
}