C++:依赖注入、循环依赖和回调

发布于 2024-10-12 03:42:46 字数 1237 浏览 7 评论 0原文

考虑以下(高度简化的)情况:

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

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

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

系统中的各个类通过调度程序发送消息。调度程序使用通信来发送。一旦返回答案,通信会将其转发回调度程序,调度程序将其调度回适当的原始发送者。 Comm 是多态的,可以从设置文件中读取要选择的实现。

调度程序依赖于通信才能发送。 Comm 依赖于调度程序才能回调。因此,这里存在循环依赖,我似乎无法实现 依赖注入 原则(即使在遇到这个不错的博客邮政)。

更新:

  1. Comm 取决于第 3 方代码(如 有各种第 3 方,Comm 是多态的)。 Comm 有接收 它有自己的功能并中继它 到调度程序的接收函数(在 实践中有多个这样的 具有各种参数的函数 套)。可能的调用是:

    CommA::receive_3(/*参数设置a*/) {
        /* 一些参数操作 */
        Dispatcher_ptr->receive_5(/*参数设置b*/);
        dispatcher_ptr->receive_6(/*参数设置c*/);
    }
    
  2. 至少当前 Dispatcher 使用在其构造函数中接收的参数“知道”要使用哪个 Comm,因此它无法在其初始化列表中初始化 Comm。它可以很容易地接收到 Comm 的共享指针并使用它来完成,但这需要首先初始化 Comm,而 Comm 需要一个指向 Dispatcher 的指针来进行回调...当然,我可以在 Comm 中实现一个名为 setDispatcher(Dispather*dispatcher_ptr) 但这不会违背依赖注入吗?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

云仙小弟 2024-10-19 03:42:46

如果 Comm 在构造时不需要调度程序,但每个 send 都接受要使用的调度程序怎么办?也就是说,

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

除非出于不同原因需要将Comm绑定到单个Dispatcher,否则这应该消除构造时循环依赖。

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.

裂开嘴轻声笑有多痛 2024-10-19 03:42:46

类之间的循环依赖并不总是一个问题,而且通常是不可避免的。博客讨论的问题是,在应用依赖注入之后,就不可能构建 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(); }
};

存在不存在问题的循环依赖关系。

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.

夜唯美灬不弃 2024-10-19 03:42:46

我意识到,在许多类使用 Dispatcher 实例的地方,只有 Dispatcher 实例使用 CommA 实例。因此,Dispatcher 对 CommA 的依赖不应该被外部化,即 CommA 对象不应该比 Dispatcher 中任何其他内部定义的变量更“注入”到 Dispatcher。

有一分钟我认为我的问题可能具有误导性,但后来我意识到它源于对依赖注入的一个非常基本的误解。 只有当依赖关系无法进行内部管理时,才应将其外部化。因此,我将这个问题和这个答案留给后代:)

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, i.e. 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 :)

真心难拥有 2024-10-19 03:42:46

我认为如果您为 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 实例)

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)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文