简体   繁体   中英

C++: Dependency injection, circular dependency and callbacks

Consider the (highly simplified) following case:

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

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

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

Various classes in the system send messages via the dispatcher. The dispatcher uses a comm to send. Once an answer is returned, the comm relays it back to the dispatcher which dispatches it back to the appropriate original sender. Comm is polymorphic and which implementation to choose can be read from a settings file.

Dispatcher has a dependency on the comm in order to send. Comm has a dependency on dispatcher in order to callback. Therefor there's a circular dependency here and I can't seem to implement the dependency injection principle (even after encountering this nice blog post).

Updates:

  1. Comm depends on 3rd party code (as there are various 3rd parties, Comm is polymorphic). Comm has a receive function of its own and it relays it to Dispatcher's receive function (in practice there are multiple such functions with various parameter sets). A possible call would be:

     CommA::receive_3(/*parameters set a*/) { /* some parameters manipulation */ dispatcher_ptr->receive_5(/*parameters set b*/); dispatcher_ptr->receive_6(/*parameters set c*/); } 
  2. At least currently Dispatcher "knows" which Comm to use using a parameter it receives in its constructor, therefor it can't initialize Comm in its initialization list. It could have just as easily received a shared_ptr to Comm and be done with it, but that would require first to initialize Comm and Comm requires a pointer to Dispatcher for the callbacks... Of course I could implement a function in Comm named setDispatcher(Dispather* dispatcher_ptr) but wouldn't that go against Dependency Injection?

What if Comm didn't require a dispatcher at construction, but each send accepted the dispatcher to use? That is,

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

Unless the Comm needs to be tied to a single Dispatcher for a different reason, this should eliminate the construction-time circular dependency.

Circular dependencies among classes is not always a problem and is often unavoidable. The issue that blog is talking about is that AFTER applying dependency injection it becomes impossible to construct A&B (because A needs a B and B needs an A, both in order to build). It's a particular kind of circular dependency.

If either A or B make sense without the other then the problem is averted: that class that doesn't NEED the other can be build without it.

For example, lets say that A is some sort of dispatcher that sends messages to an arbitrary set of B objects:

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(); }
};

There's a circular dependency that doesn't have the problem.

I realized that where many classes use the Dispatcher instance, only the Dispatcher instance uses the CommA instance. Therefore the Dispatcher's dependency on CommA should not be externalized, ie CommA object should not be "injected" to the Dispatcher any more than any other internally defined variable within Dispatcher.

For a minute I thought my question might have been misleading, but then I realized that it originated from a very basic misunderstanding about Dependency Injection. Dependencies should be externalized only if they cannot be internally managed . Therefore I'm leaving this question and this answer for posterity :)

i think if you provide a separate interface class for Dispatcher, say DispatcherInterface following your name convention, the cyclic dependency should be gone, since now you can create third components (like DispatcherCommProviderInterface ) the implementations of this interface can know about both Comm and Dispatcher, but both Comm nor Dispatcher will not know anything about such DispatcherCommProvider implementations (at most they will know about their interface)

Interfaces:

// 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;
}

Implementations:

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


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

now your dependency injection strategy only needs to take care of creating the appropiate DispatcherCommProviderInterface implementation (and probably wiring it to the Comm and Dispatcher instances)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM