堆栈分配RAII对象与DI原理

时间:2011-10-18 12:10:28

标签: c++ unit-testing dependency-injection raii

在C ++中,我经常使用RAII样式的对象使代码更可靠,并在堆栈上分配它们以使代码更高效(并避免使用bad_alloc)。

但是在堆栈上创建具体类的对象会违反依赖性反转(DI)原则并阻止模拟此对象。

请考虑以下代码:

struct IInputStream
{
    virtual vector<BYTE> read(size_t n) = 0;
};

class Connection : public IInputStream
{
public:
    Connection(string address);
    virtual vector<BYTE> read(size_t n) override;
};

struct IBar
{
    virtual void process(IInputStream& stream) = 0;
};

void Some::foo(string address, IBar& bar)
{
    onBeforeConnectionCreated();
    {
        Connection conn(address);
        onConnectionCreated();
        bar.process(conn);
    }
    onConnectionClosed();
}

我可以测试IBar::process,但我也想测试Some::foo,而不创建真正的Connection对象。

当然我可以使用工厂,但它会使代码复杂化并引入堆分配 另外,我不想添加Connection::open方法,我更喜欢构建完全初始化且功能齐全的对象。

我会ConnectionSome键入模板参数(或foo如果将其作为自由函数提取),但我不确定它是否正确(模板)看起来像很多人的黑魔法,所以我更喜欢使用动态多态)

3 个答案:

答案 0 :(得分:6)

你现在正在做的是“强制耦合”RAII类和服务提供者类(如果你想要可测试性,它应该真的是一个接口)。通过以下方式解决这个问题:

  1. Connection抽象为IConnection
  2. 有一个单独的ScopedConnection类,可以在
  3. 之上提供RAII

    例如:

    void Some::foo(string address, IBar& bar)
    {
        onBeforeConnectionCreated();
        {
            ScopedConnection conn(this->pFactory->getConnection());
            onConnectionCreated();
            bar.process(conn);
        }
        onConnectionClosed();
    }
    

答案 1 :(得分:1)

通过“我可以使用工厂,但它会显着地使代码复杂化并引入堆分配”我的意思是以下步骤:

创建抽象类并从中派生Connection

struct AConnection : IInputStream
{
    virtual ~AConnection() {}
};

将工厂方法添加到Some

class Some
{
.....
protected:
    VIRTUAL_UNDER_TEST AConnection* createConnection(string address);
};

用智能指针替换堆栈分配的连接

unique_ptr<AConnection> conn(createConnection(address));

答案 2 :(得分:1)

要在实际实现和模拟实现之间进行选择,您必须以某种方式注入要构造的实际类型。我推荐的方法是将类型作为可选模板参数注入。它允许您像以前一样不引人注意地使用Some::foo,但允许您在测试时交换创建的连接。

template<typename ConnectionT=Connection> // models InputStream
void Some::foo(string address, IBar& bar)
{
    onBeforeConnectionCreated();
    {
        ConnectionT conn(address);
        onConnectionCreated();
        bar.process(conn);
    }
    onConnectionClosed();
}

如果您在编译时知道实际类型,我不会创建工厂和运行时多态的开销。