栈分配RAII对象vs DI原则

Stack allocated RAII objects vs DI principle

本文关键字:DI 原则 vs 对象 分配 RAII      更新时间:2023-10-16

在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::open方法,我更喜欢构造完全初始化和功能齐全的对象。

我会使Connection类型为Some模板参数(或foo,如果提取它作为一个自由函数),但我不确定这是正确的方式(模板看起来像一个黑魔法对许多人来说,所以我更喜欢使用动态多态性)

您现在正在做的是"强制耦合"RAII类和服务提供者类(如果您想要可测试性,它实际上应该是一个接口)。通过:

  1. Connection抽象为IConnection
  2. 有一个单独的ScopedConnection类,在上面提供RAII
例如:

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

"我可以使用工厂,但它会显著地使代码复杂化并引入堆分配",我指的是以下步骤:

创建抽象类并从中派生Connection

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

Some添加工厂方法

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

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

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

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

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

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