堆栈分配的 RAII 对象与 DI 原理
在 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
如果将其提取为自由函数),但我不确定这是正确的方式(模板对很多人来说就像黑魔法,所以我更喜欢使用动态多态性)
In C++ I often use RAII-style objects to make code more reliable and allocate them on stack to make code more performant (and to avoid bad_alloc).
But creating an object of concrete class on stack violates the dependency inversion (DI) principle and prevents mocking this object.
Consider the following code:
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();
}
I can test IBar::process
, but I also want to test Some::foo
, without creating real Connection object.
Surely I can use a factory, but it will significantly complicate code and introduce heap-allocation.
Also, I don't like to add the Connection::open
method, I prefer to construct completely initialized and fully functional objects.
I would make Connection
type a template parameter for Some
(or for foo
if extract it as a free function), but I'm not sure that it's right way (templates look like a black magic to many people, so I prefer to use dynamic polymorphism)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
您现在正在做的是“强制耦合”RAII 类和服务提供者类(如果您想要可测试性,它实际上应该是一个接口)。通过以下方法解决此问题:
Connection
抽象为IConnection
,ScopedConnection
类,该类在此基础上提供 RAII例如:
What you are doing right now is "force-coupling" the RAII class and the service provider class (which, if you want testability, should really be an interface instead). Address this by:
Connection
intoIConnection
ScopedConnection
class that provides RAII on top of thatFor example:
“我可以使用工厂,但它会使代码显着复杂化并引入堆分配”,我的意思是以下步骤:
创建抽象类并从中派生
Connection
将工厂方法添加到
Some< /code>
用智能指针替换堆栈分配的连接
By "I can use a factory, but it will significally complicate code and introduce heap-allocation" I meant the following steps:
Create abstract class and derive
Connection
from itAdd factory method to
Some
Replace stack-allocated connecton by smart pointer
要在实际实现和模拟实现之间进行选择,您必须注入要以某种方式构造的实际类型。我建议的方法是将类型作为可选模板参数注入。它允许您像以前一样不显眼地使用
Some::foo
,但允许您在测试时交换创建的连接。如果您在编译时知道实际类型,我就不会创建工厂和运行时多态性的开销。
To chose between your actual implementation and the mocked one, you have to inject the actual type that you want to construct in some fashion. The way I'd recommend is injecting the type as an optional template parameter. It allows you to unobtrusively use
Some::foo
as you used to, but enables you to swap the created connection in case of a test.I would not create the overhead of a factory and runtime polymorphism if you know the actual type at compile time.