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

发布于 2024-09-08 08:10:26 字数 1070 浏览 4 评论 0原文

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

I'm working on a library that defines a client interface for some service. Under the hood I have to validate the data provided by users and then pass it to "engine" process using Connection class from another library (note: the Connection class isn't known to the users of our library). One of my colleagues proposed using 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;
}

However, I find it very hard to test - even if I link my tests to some mocked implementation of Connection, I don't have an easy access to it to set and validate expectations. Am I missing something, or the much cleaner and testable solution is using 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();
}

Are there any reasons to go with PIMPL in this situation?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

阿楠 2024-09-15 08:10:26

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

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

AFAIK the usual reason to use the Pimpl idiom is to reduce compile/link time dependencies to the implementation of the class (by removing the implementation details from the public header file altogether). Another reason may be to enable the class to change its behaviour dynamically (aka the State pattern).

The second does not seem to be the case here, and the first can be achieved with inheritance + a factory as well. However, as you noted, the latter solution is much easier to unit test, so I would prefer this.

天冷不及心凉 2024-09-15 08:10:26

GoTW15

GoTW28

来自赫伯·萨特。很好的指导,可以帮助您入门。

GoTW15

GoTW28

From Herb Sutter. Good pointers to get you started.

温馨耳语 2024-09-15 08:10:26

是的,这是使用 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。

Yes this is a good place to use the Pimpl pattern, and yes it will be difficult to test as is.

The problem is that the two concepts oppose one another:

  • Pimpl is about hiding dependencies from the client: this reduces compile/link time and is better from an ABI stability point of view.
  • Unit Testing is usually about surgical intervention in the dependencies (use of mock ups, for example)

However, it does not mean that you should sacrifice one for another. It merely means that you should adapt your code.

Now, what if Connection was implemented with the same idiom ?

class Connection
{
private:
  ConnectionImpl* mImpl;
};

And delivered through a Factory:

// Production code:

Client client = factory.GetClient();

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

This way, you can access the nitty gritty details of your test implement of connection while testing client without exposing the implementation to the client or breaking the ABI.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文