这是一个理论问题,而不是实际问题。

我正在尝试设计一个遵循软件最佳实践的程序。但是我在寻找最好的方法来做某些事情时遇到了麻烦,这些事情可以进行测试驱动开发,良好的封装和抽象,同时还可以最小化耦合。

我计划有两个抽象层,一个数据库管理层和一个表管理层。

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();
}

10-05 22:24