Стек, выделенный объектам RAII против принципа DI - PullRequest
6 голосов
/ 18 октября 2011

В C ++ я часто использую объекты в стиле RAII, чтобы сделать код более надежным, и размещать их в стеке, чтобы сделать код более производительным (и избежать bad_alloc).

Но создание объекта конкретного класса в стеке нарушает принцип инверсии зависимостей (DI) и предотвращает насмешку над этим объектом.

Рассмотрим следующий код:

struct IInputStream
{
    virtual vector<BYTE> read(size_t n) = 0;
};

class Connection : public IInputStream
{
public:
    Connection(string address);
    virtual vector<BYTE> read(size_t n) override;
};

struct IBar
{
    virtual void process(IInputStream& stream) = 0;
};

void Some::foo(string address, IBar& bar)
{
    onBeforeConnectionCreated();
    {
        Connection conn(address);
        onConnectionCreated();
        bar.process(conn);
    }
    onConnectionClosed();
}

IМожно протестировать IBar::process, но я также хочу протестировать Some::foo, не создавая настоящий объект Соединения.

Конечно, я могу использовать фабрику, но это значительно усложнит код и введет распределение кучи.
Кроме того, я не люблю добавлять метод Connection::open, я предпочитаю создавать полностью инициализированные и полностью функциональные объекты.

Я бы сделал Connection тип параметра шаблона для Some (или для foo если извлечь его как бесплатную функцию), но я не уверен, что это правильный путь (для многих шаблоны выглядят как чёрная магия, поэтому я предпочитаю использовать динамический полиморфизм)

Ответы [ 3 ]

6 голосов
/ 18 октября 2011

То, что вы делаете сейчас, это «принудительное соединение» класса RAII и класса провайдера услуг (который, если вы хотите тестируемость, должен действительно быть интерфейсом). Адрес это по:

  1. абстрагирование Connection в IConnection
  2. имеет отдельный класс ScopedConnection, который обеспечивает RAII поверх этого

Например:

void Some::foo(string address, IBar& bar)
{
    onBeforeConnectionCreated();
    {
        ScopedConnection conn(this->pFactory->getConnection());
        onConnectionCreated();
        bar.process(conn);
    }
    onConnectionClosed();
}
1 голос
/ 28 октября 2015

Чтобы выбрать между вашей фактической реализацией и фиктивной, вы должны ввести фактический тип, который вы хотите создать каким-либо образом.Я бы порекомендовал внедрить тип в качестве необязательного параметра шаблона.Это позволяет вам ненавязчиво использовать Some::foo, как вы это делали раньше, но позволяет вам менять местами созданное соединение в случае теста.

template<typename ConnectionT=Connection> // models InputStream
void Some::foo(string address, IBar& bar)
{
    onBeforeConnectionCreated();
    {
        ConnectionT conn(address);
        onConnectionCreated();
        bar.process(conn);
    }
    onConnectionClosed();
}

Я бы не стал создавать накладные расходы для фабричного и динамического полиморфизма, есливы знаете фактический тип во время компиляции.

1 голос
/ 18 октября 2011

Под «я могу использовать фабрику, но это значительно усложнит код и введет распределение кучи», я имел в виду следующие шаги:

Создание абстрактного класса и извлечение из него Connection

struct AConnection : IInputStream
{
    virtual ~AConnection() {}
};

Добавить фабричный метод к Some

class Some
{
.....
protected:
    VIRTUAL_UNDER_TEST AConnection* createConnection(string address);
};

Заменить выделенное стеком соединение интеллектуальным указателем

unique_ptr<AConnection> conn(createConnection(address));
...