4

Context

There is a modular C++ application. Each module provides an interface (an abstract class) which is used by other modules. There are no circular dependencies. The module interaction happens via direct method calls in one way, and via events (a variation of the observer pattern, similar to Qt's signals) in the other way.

For example:

// an interface towards the battery manager
class IBatteryManager
{
public:
    // get the battery level
    virtual int batteryLevel() = 0;
    // the system is about to run out of battery and enter
    // a power saving mode
    virtual Event<> &aboutToRunOutOfBattery() = 0;
};

The Event class looks like this:

template<typename... tArgs>
class Event
{
public:
    // client: the provided function will be called when this
    //         event is emitted
    void subscribe(std::function<void(tArgs...)>);
    // server: emit the event
    void notify(tArgs...);
};

Problem and Possible Solution

Sometimes a fire-and-forget event is not enough. A module might want to notify its clients about some event and wait until it is processed (and maybe get back some values).

For instance, BatteryManager might want to wait until all clients have processed the about-to-run-out-of-battery event before entering the power saving mode.

I can imagine multiple ways how this can be done. One of them would be to extend the Event class with the future/promise pattern in the following way:

template<typename tValue, typename... tArgs>
class Event
{
public:
    // client: now must return some value
    void subscribe(std::function<Future<tValue>(tArgs...)>);
    // server: this future is fulfilled as soon as all clients
    //         have completed their promises
    Future<std::vector<tValue>> notify(tArgs...);
};

Questions

Updated: I have rephrased questions a bit to be more clear.

Is there a design pattern which describes the approach shown above? In other words, does such approach have a commonly accepted name, vocabulary, pros/cons, a canonical implementation, a list of things one needs to pay attention to, and other stuff what patterns usually have?

I am asking because this might be not a problem which is unique to me. So I do not want to end up reinventing the wheel, both in terms of implementation (by probably re-doing someone else's mistakes) and terminology (by introducing my own terms, which will make the communication with other developers harder).

Secondary questions: Are there maybe better approaches? What would be the best practice in this case?

10
  • 3
    Possible duplicate of Choosing the right Design Pattern
    – gnat
    Oct 30, 2017 at 16:00
  • What's wrong with the "design pattern" you've come up with (namely, replacing void with an actual return value)? Oct 30, 2017 at 16:46
  • @RobertHarvey, there is nothing wrong with it at the moment (I have not started implementing it yet). I am basically asking if it has a commonly accepted name, vocabulary and maybe a canonical implementation etc.? I just do not want to end up reinventing the wheel.
    – Kane
    Oct 30, 2017 at 16:57
  • @Kane: Why don't you just call the method directly? The point of events is you don't know of care if anybody reacts to them. But in this case you require somebody to react, so why not just call them directly?
    – JacquesB
    Oct 30, 2017 at 17:21
  • @Kane: So far, what I see here is just sensible object-oriented design. What you're doing here (adding a return type) is no different than what legions of object-oriented developers have been doing since C++ was first invented. Oct 30, 2017 at 17:32

1 Answer 1

1

The point of the "Event" pattern is the source does not know or care if any listeners are attached. The source is decoupled from the clients. Events often are executed synchronously, but the source should not know about that either.

But in your case you want to wait for a return value, which means you are depending on the client and you are making assumptions about the behavior of the client (otherwise you wouldn't care if it had finished processing or not). So you have a straightforward dependency, which means the source should just go ahead and call the method directly - skip all the indirection with subscription and whatnot.

In short, what you are trying to do is not a recognized as a "pattern", since it is so simple: You want to call a method on another object.

10
  • The source (BatteryManager in this example) does not care if and how many listeners are attached. It only cares if all attached listeners (whatever the count is) have processed the request. If there are no listeners, notify() just returns an empty vector. At least this is my current vision. Could you please update your answer with some code examples? I do not really understand yet what your proposal is. Do you suggest to inherit all clients from, say, ILowBatteryObserver, which would have a react() method which is going to be called by the source?
    – Kane
    Oct 30, 2017 at 19:23
  • @Kane: I'm just suggesting BattteryManager directly call the method(s) you want called, nothing else. No need for inheritance or subscriptions or patterns or code examples - you just call the method! Nothing in your question suggest you need anything more complex than this.
    – JacquesB
    Oct 30, 2017 at 20:41
  • The source does not know who its clients are. Thus, it does not know which method and on which objects to call. If you mean BatteryManager(ClientA *a, ClientB *b, ClientC *c) and a->doThis(); b->doThat(); c->doStuff();, then it is not scalable and introduces circular dependencies between modules.
    – Kane
    Oct 30, 2017 at 21:09
  • @Kane: I will ague something is off in the way you state the problem. You cannot on one hand state the source does not know anything about the clients, and on the other hand wait for the clients return a value. How can the source handle the returned value in a meaningful way if it doesn't know anything about what it means? If the source depend on the clients process the message synchronously, then it depends on certain behavior in the clients. So I am pointing out that the source do make assumptions about the clients, which means they are not decoupled. You already have the dependency.
    – JacquesB
    Oct 31, 2017 at 7:19
  • 1
    The source does not do any assumptions about clients. The source defines the interface with the event with an acknowledgement mechanism. The contract is "whoever you are, if you are interested in this event, you will have to asynchronously send back the confirmation". So the source enforces the specific behaviour of its clients. Yet it does not care what the clients are and how do they handle the event.
    – Kane
    Oct 31, 2017 at 10:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.