我正在开发一个为某些服务定义客户端接口的库。在引擎盖下,我必须验证用户提供的数据,然后使用来自另一个库的Connection类将其传递给“引擎”进程(注意:我们的库的用户不知道Connection类)。我的一位同事建议使用PIMPL:
class Client {
public:
Client();
void sendStuff(const Stuff &stuff) {_pimpl->sendStuff(stuff);}
Stuff getStuff(const StuffId &id) {return _pimpl->getStuff(id);}
private:
ClientImpl *_pimpl;
}
class ClientImpl { // not exported
public:
void sendStuff(const Stuff &stuff);
Stuff getStuff(const StuffId &id);
private:
Connection _connection;
}
但是,我觉得很难测试 - 即使我将测试链接到一些模拟的Connection实现,我也无法轻松访问它来设置和验证期望。我错过了什么,或者更清洁,更可测试的解决方案是使用interface + factory:
class ClientInterface {
public:
void sendStuff(const Stuff &stuff) = 0;
Stuff getStuff(const StuffId &id) = 0;
}
class ClientImplementation : public ClientInterface { // not exported
public:
ClientImplementation(Connection *connection);
// +implementation of ClientInterface
}
class ClientFactory {
static ClientInterface *create();
}
在这种情况下,有没有理由选择PIMPL?
答案 0 :(得分:4)
AFAIK使用Pimpl习惯用法的通常原因是减少编译/链接时间依赖于类的实现(通过从公共头文件中完全删除实现细节)。另一个原因可能是让类动态地改变它的行为(也就是State pattern)。
第二种似乎不是这种情况,第一种可以通过继承+工厂来实现。但是,正如您所指出的,后一种解决方案更容易进行单元测试,所以我更喜欢这个。
答案 1 :(得分:1)
答案 2 :(得分:0)
是的,这是使用Pimpl模式的好地方,是的,很难按原样进行测试。
问题在于这两个概念相互对立:
然而,这并不意味着你应该牺牲一个人。它只是意味着你应该调整你的代码。
现在,如果Connection
使用相同的成语实现了什么呢?
class Connection
{
private:
ConnectionImpl* mImpl;
};
通过工厂交付:
// Production code:
Client client = factory.GetClient();
// Test code:
MyTestConnectionImpl impl;
Client client = factory.GetClient(impl);
这样,您可以在测试客户端时访问测试连接实现的细节,而不会将实现暴露给客户端或破坏ABI。