Действительно ли это - хорошее место для использования шаблона PIMPL?

Я работаю над библиотекой, которая определяет клиентский интерфейс для некоторого сервиса. Под капотом я должен проверить данные, обеспеченные пользователями, и затем передать его процессу "механизма" с помощью класса Соединения из другой библиотеки (примечание: класс Соединения не известен пользователям нашей библиотеки). Один из моих коллег предложил использовать 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;
}

Однако мне очень трудно протестировать - даже если я связываю свои тесты с некоторой дразнившей реализацией Соединения, у меня нет легкого доступа к нему, чтобы установить и проверить ожидания. Я пропускающий что-то, или намного более чистое и тестируемое решение использует интерфейс + фабрика:

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 в этой ситуации?

5
задан chalup 7 July 2010 в 11:29
поделиться

3 ответа

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

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

4
ответ дан 14 December 2019 в 18:55
поделиться

Да, это хорошее место для использования паттерна Pimpl, и да, это будет трудно протестировать в существующем виде.

Проблема в том, что эти две концепции противостоят друг другу:

  • Pimpl - это скрытие зависимостей от клиента: это сокращает время компиляции/линковки и лучше с точки зрения стабильности ABI.
  • Unit Testing обычно предполагает хирургическое вмешательство в зависимости (использование макетов, например)

Однако это не означает, что вы должны жертвовать одним ради другого. Это просто означает, что вы должны адаптировать свой код.

Итак, что если Connection будет реализован с использованием той же идиомы?

class Connection
{
private:
  ConnectionImpl* mImpl;
};

И поставляться через Factory:

// Production code:

Client client = factory.GetClient();

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

Таким образом, вы сможете получить доступ к мельчайшим деталям вашей тестовой реализации соединения во время тестирования клиента, не раскрывая реализацию клиенту и не нарушая ABI.

0
ответ дан 14 December 2019 в 18:55
поделиться

GoTW15

GoTW28

От Херба Саттера. Хорошие рекомендации для начала работы.

1
ответ дан 14 December 2019 в 18:55
поделиться
Другие вопросы по тегам:

Похожие вопросы: