这是使用PIMPL模式的好地方吗?

时间:2010-07-07 11:29:33

标签: c++ unit-testing pimpl-idiom

我正在开发一个为某些服务定义客户端接口的库。在引擎盖下,我必须验证用户提供的数据,然后使用来自另一个库的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?

3 个答案:

答案 0 :(得分:4)

AFAIK使用Pimpl习惯用法的通常原因是减少编译/链接时间依赖于类的实现(通过从公共头文件中完全删除实现细节)。另一个原因可能是让类动态地改变它的行为(也就是State pattern)。

第二种似乎不是这种情况,第一种可以通过继承+工厂来实现。但是,正如您所指出的,后一种解决方案更容易进行单元测试,所以我更喜欢这个。

答案 1 :(得分:1)

GoTW15

GoTW28

来自Herb Sutter。帮助你入门的好指点。

答案 2 :(得分:0)

是的,这是使用Pimpl模式的好地方,是的,很难按原样进行测试。

问题在于这两个概念相互对立:

  • Pimpl是关于隐藏客户端的依赖关系:这会减少编译/链接时间,从ABI稳定性的角度来看更好。
  • 单元测试通常是关于依赖项的外科干预(例如,使用模拟)

然而,这并不意味着你应该牺牲一个人。它只是意味着你应该调整你的代码。

现在,如果Connection使用相同的成语实现了什么呢?

class Connection
{
private:
  ConnectionImpl* mImpl;
};

通过工厂交付:

// Production code:

Client client = factory.GetClient();

// Test code:
MyTestConnectionImpl impl;
Client client = factory.GetClient(impl);

这样,您可以在测试客户端时访问测试连接实现的细节,而不会将实现暴露给客户端或破坏ABI。