在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
方法,我更喜欢构建完全初始化且功能齐全的对象。
我会Connection
为Some
键入模板参数(或foo
如果将其作为自由函数提取),但我不确定它是否正确(模板)看起来像很多人的黑魔法,所以我更喜欢使用动态多态)
答案 0 :(得分:6)
你现在正在做的是“强制耦合”RAII类和服务提供者类(如果你想要可测试性,它应该真的是一个接口)。通过以下方式解决这个问题:
Connection
抽象为IConnection
ScopedConnection
类,可以在例如:
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();
}
如果您在编译时知道实际类型,我不会创建工厂和运行时多态的开销。