C ++: внедрение зависимостей, циклическая зависимость и обратные вызовы - PullRequest
2 голосов
/ 14 января 2011

Рассмотрим (очень упрощенный) следующий случай:

class Dispatcher {
public:
    receive() {/*implementation*/};  // callback
}

class CommInterface {
public:
    send() = 0;  // call
}

class CommA : public CommInterface {
public:
    send() {/*implementation*/};
}

Различные классы в системе отправляют сообщения через диспетчер. Диспетчер использует комм для отправки. Как только ответ возвращается, comm передает его обратно диспетчеру, который отправляет его соответствующему исходному отправителю. Comm полиморфен, и какую реализацию выбрать можно прочитать из файла настроек.

Диспетчер зависит от связи для отправки. Comm имеет зависимость от диспетчера для обратного вызова. Поэтому здесь есть круговая зависимость, и я не могу реализовать принцип внедрения зависимостей (даже после встречи с этим хорошим постом в блоге).

Обновление:

  1. Связь зависит от стороннего кода (как Есть различные третьи стороны, Comm полиморфен). Comm имеет получение функция сама по себе, и это передает к функции приема Диспетчера (в На практике есть несколько таких функции с различными параметрами наборы). Возможный звонок будет:

    CommA::receive_3(/*parameters set a*/) {
        /* some parameters manipulation */
        dispatcher_ptr->receive_5(/*parameters set b*/);
        dispatcher_ptr->receive_6(/*parameters set c*/);
    }
    
  2. По крайней мере, в настоящее время Dispatcher «знает», какой Comm использовать, используя параметр, который он получает в своем конструкторе, поэтому он не может инициализировать Comm в своем списке инициализации. Он мог бы так же легко получить shared_ptr в Comm и покончить с ним, но для этого потребуется сначала инициализировать Comm, а Comm требует указатель на Dispatcher для обратных вызовов ... Конечно, я мог бы реализовать функцию в Comm с именем setDispatcher(Dispather* dispatcher_ptr) но разве это не пошло бы против инъекции зависимости?

Ответы [ 4 ]

0 голосов
/ 28 июня 2011

Я думаю, что если вы предоставите отдельный интерфейсный класс для Dispatcher, скажем DispatcherInterface, следуя вашему соглашению об именах, циклическая зависимость должна исчезнуть, поскольку теперь вы можете создавать третьи компоненты (например, DispatcherCommProviderInterface), реализации этого интерфейса могутзнать о Comm и Dispatcher, но ни Comm, ни Dispatcher не будут ничего знать о таких реализациях DispatcherCommProvider (в большинстве случаев они будут знать об их интерфейсе)

Интерфейсы:

// does not know anything about DispatcherCommProviderInterface  or CommInterface 
class DispatcherInterface {
public:
    receive() = 0;  // callback
}

// does not know anything about DispatcherCommProviderInterface  or DispatcherInterface
class CommInterface {
public:
    send() = 0;  // call
}


class DispatcherCommProviderInterface {
public:
    CommInterface* getComm() = 0;  
    DispatcherInterface* getDispatcher() = 0; 
    void setComm(CommInterface*) = 0;  
    void setDispatcher(DispatcherInterface*) = 0;
}

Реализации:

class CommA : public CommInterface {
public:
    send() {/*implementation using some DispatcherCommProviderInterface  */};
}


class Dispatcher : public DispatcherInterface  {
public:
    receive() {/*implementation using some DispatcherCommProviderInterface  */};  // callback
}

теперь вашей стратегии внедрения зависимостей нужно всего лишь позаботиться о создании подходящей реализации DispatcherCommProviderInterface (и, возможно, ее подключении к экземплярам Comm и Dispatcher)

0 голосов
/ 14 января 2011

Круговые зависимости между классами не всегда являются проблемой и часто неизбежны. Проблема, о которой говорит блог, заключается в том, что ПОСЛЕ применения внедрения зависимости становится невозможным построить A & B (потому что A нужен B, а B нужен A, оба для построения). Это особый вид круговой зависимости.

Если A или B имеют смысл без другого, тогда проблема устранена: тот класс, который НЕ НУЖЕН другому, может быть создан без него.

Например, допустим, что A - это своего рода диспетчер, который отправляет сообщения в произвольный набор объектов B:

struct A
{
  A() {}
  void add_b(B const& b) { bs.push_back(b); }
  void dispatch(int num) { std::for_each(bs.begin(), bs.end(), [num](B & b) { b.message(num); }); }
  void something_b_uses();
};

struct B
{
  B(A* a) : my_a(a) {}
 ...
  void message(int num) { a->something_b_uses(); }
};

Есть круговая зависимость, у которой нет проблемы.

0 голосов
/ 14 января 2011

Я понял, что там, где многие классы используют экземпляр Dispatcher, только экземпляр Dispatcher использует экземпляр CommA. Следовательно, зависимость Dispatcher от CommA не должна быть экстернализована, т. Е. Объект CommA не должен «инжектироваться» в Dispatcher, как любая другая внутренне определенная переменная в Dispatcher.

В течение минуты я думал, что мой вопрос мог вводить в заблуждение, но затем я понял, что он возник из очень простого недопонимания относительно инъекции зависимости. Зависимости следует извлекать только в том случае, если ими нельзя управлять изнутри . Поэтому я оставляю этот вопрос и этот ответ для потомков:)

0 голосов
/ 14 января 2011

Что если Comm не требует диспетчера при строительстве, но каждый send соглашается использовать диспетчера?То есть

class Comm
{
    virtual void send(Dispatcher *d) = 0;
};

Если Comm не нужно привязать к одному Dispatcher по другой причине, это должно устранить циклическую зависимость времени строительства.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...