5

我正在开发一个为某些服务定义客户端接口的库。在后台,我必须验证用户提供的数据,然后使用来自另一个库的 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 的一些模拟实现,我也无法轻松访问它来设置和验证期望。我是否遗漏了什么,或者更清洁和可测试的解决方案是使用接口 + 工厂:

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?

4

3 回答 3

4

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

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

于 2010-07-07T11:54:54.207 回答
1

去TW15

GoTW28

来自赫伯萨特。让您入门的好建议。

于 2010-07-07T11:54:09.910 回答
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。

于 2010-07-07T13:51:19.820 回答