Это хорошее место, чтобы использовать шаблон PIMPL? - PullRequest
5 голосов
/ 07 июля 2010

Я работаю над библиотекой, которая определяет интерфейс клиента для некоторого сервиса. Под капотом я должен проверить данные, предоставленные пользователями, а затем передать их процессу «движок», используя класс 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 в этой ситуации?

Ответы [ 3 ]

4 голосов
/ 07 июля 2010

AFAIK обычной причиной использования идиомы Pimpl является уменьшение зависимости времени компиляции / компоновки для реализации класса (путем полного удаления деталей реализации из открытого заголовочного файла).Другая причина может заключаться в том, чтобы позволить классу динамически изменять свое поведение (он же шаблон состояния ).

Второе, похоже, не так, и первое может быть достигнутонаследование + фабрика тоже.Однако, как вы заметили, последнее решение гораздо проще для модульного тестирования, поэтому я бы предпочел это.

1 голос
/ 07 июля 2010

GoTW15

GoTW28

От Херба Саттера. Хорошие указатели для начала.

0 голосов
/ 07 июля 2010

Да, это хорошее место для использования паттерна 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.

...