3

I'm trying to create an observer pattern that the subject notifies the observers with different notifications.

Normally in observer pattern implementations you can see only one method called notify where it notifies the observers that something happened and has a kind of inversion where the observer holds the pointer of the subject and ask the subject for something when it's notified.

I'm implementing this a little different, where the subject attaches the observers and notify all of them without the needed of holding the pointer of a subject inside of the observers. For example:

#include <iostream>
#include <vector>

class ObserverEvents
{
public:
    virtual addSomethingOne(int) = 0;
    virtual addSomethingTwo(float) = 0;
};

class Observer : public ObserverEvents
{
public:
    Observer();
    ~Observer();

    virtual addSomethingOne(int) {}
    virtual addSomethingTwo(float) {}
};

class Subject : public ObserverEvents
{
public:
    Subject() {}

    ~Subject()
    {
        for (int i = 0; i < observers.size(); ++i)
        {
            delete observers[i];
        }
    }

    void attach(Observer * observer)
    {
        observers.push_back(observer);
    }

    virtual addSomethingOne(int something)
    {
        for (int i = 0; i < observers.size(); ++i)
        {
            observers[i].addSomethingOne(something);
        }
    }

    virtual addSomethingTwo(float something)
    {
        for (int i = 0; i < observers.size(); ++i)
        {
            observers[i].addSomethingTwo(something);
        }
    }
private:
    std::vector<Observer *> observers;
};

class FooObserver : public Observer
{
public:
    BarObserver() {}
    ~BarObserver() {}

    addSomethingOne(int something)
    {
        // do something with something
    }

    addSomethingTwo(float something)
    {
        // do something with something
    }
};

class BarObserver : public Observer
{
public:
    BizObserver() {}
    ~BizObserver() {}

    addSomethingOne(int something)
    {
        // do something with something
    }

    addSomethingTwo(float something)
    {
        // do something with something
    }
};

int main(int argc, char const * argv[])
{
    Subject subject;
    subject.attach(new FooObserver());
    subject.attach(new BarObserver());

    return 0;
}

The only thing I'm concern is if I'm not broking any design principle like the Open and Closed or something similar, and also if I need to add a new notification I need to implement in all other classes. (which is painful - imagine 10 observers or even more).

I was thinking in make this different, create only one interface and then I can inherit it creating other notifications but there's a problem, how can the observers determine what each different type of notification are?

Example:

#include <iostream>
#include <vector>

class Notifier
{
public:
    Notifier() {}
    ~Notifier() {}

    virtual int getInt() const = 0;
};

class FooNotifier
{
public:
    FooNotifier() {}
    ~FooNotifier() {}

    int getInt() const
    {
        return 10;
    }
};

class BarNotifier
{
public:
    BarNotifier() {}
    ~BarNotifier() {}

    int getInt() const
    {
        return 50;
    }
};

class Observer : public ObserverEvents
{
public:
    Observer();
    ~Observer();

    virtual receive(Notifier *) = 0;
};

class Subject : public ObserverEvents
{
public:
    Subject() {}

    ~Subject()
    {
        for (int i = 0; i < observers.size(); ++i)
        {
            delete observers[i];
        }
    }

    void attach(Observer * observer)
    {
        observers.push_back(observer);
    }

    virtual notify(Notifier * notification)
    {
        for (int i = 0; i < observers.size(); ++i)
        {
            observers[i].receive(notification);
        }
    }
private:
    std::vector<Observer *> observers;
};

class FooObserver : public Observer
{
public:
    BarObserver() {}
    ~BarObserver() {}

    receive(Notifier * notification)
    {
        // ...
    }
};

class BarObserver : public Observer
{
public:
    BizObserver() {}
    ~BizObserver() {}

    receive(Notifier * notification)
    {
        // ...
    }
};

int main(int argc, char const * argv[])
{
    Subject subject;
    subject.attach(new FooObserver());
    subject.attach(new BarObserver());

    subject.notify(new FooNotifier());
    subject.notify(new BarNotifier());

    return 0;
}

The implementation is just an example, I know that I can be using smart pointers, deleting raw pointers and doing things better, but it's JUST an example of implementation.

The problem with this new approach is that I will need to know if Notifier's API in order to use it inside of the Observer, to call getInt.

How can I do this, what is the best way for doing that? How can I send different notifications to the observers?

yayuj
  • 2,194
  • 3
  • 17
  • 29
  • Seems you went the Java way. Java does it their way, because the language is so restricted, not because it is good. We have pointers, lambdas and functors. – Deduplicator Nov 09 '14 at 22:14
  • Actually I don't know, I never used Java before. – yayuj Nov 09 '14 at 22:42

3 Answers3

3
template<class C> class event {
    C list;
public:
    template<class... ARGS> inline void add(ARGS&&... args)
    {list.emplace_back(std::forward<ARGS>(args)...);}
    template<class... ARGS> inline void notifyAll(ARGS&&... args)
    {for(auto&& x : list) x(args...);}
};

A small template for an event, which only supports adding listeners and calling them all.

Instantiate like

event<std::vector<void(*)()>> a;
event<std::vector<std::function<void()>>> a;

Or with any other containter of callables.

A useful change would be using a container of std::tuple<tag, callable>, where the tag must be given on adding, and can be used to remove a listener.

template<class F, class T=void*> class event {
    std::vector<std::pair<T, F>> v;
public:
    template<class... ARGS> inline void add(T tag, ARGS&&... args)
    {v.emplace_back(std::piecewise_construct, std::make_tuple(tag),
         std::forward_as_tuple(std::forward<ARGS>(args)...));}
    template<class... ARGS> inline void notifyAll(ARGS&&... args)
    {for(auto&& x : v) x(args...);}
    void remove(T tag) {v.erase(std::remove_if(v.begin(), v.end(),
        [=](const std::pair<T, F>& x){return x.first()==tag;}), v.end());}
};

The latter is instantiated with a functor-type, like std::function<...> or a function-pointer.

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
  • @Yakk: Corrected the issue. – Deduplicator Nov 10 '14 at 00:05
  • Your code is good to be used in some cases but I think that it's not useful yet. If you understand what I was saying above I want the subjects to receive different types of notifications. Imagine this situation: A notification `Animal` I can inherent and make `Cat` and `Animal`. In animal I have three possible situations: dead, alive and sick (just an example). What I want to do is: When the observers/listeners receive one of those notifications knows if the animal is dead, alive or sick. - As I said above, there were two ways, the first one is creating a method for each state... – yayuj Nov 10 '14 at 01:52
  • And the other is to create as I'm saying an object to each notification with states. The thing is: If I add a new state what happens? Imagine using switch in order to detect which state is the animal. It would be broking the Open and Closed. Right? - That is the question: The best way passing notifications and knowing the state/situation of that notification in the observer/listener without broking any principle. – yayuj Nov 10 '14 at 01:54
  • You can decide which parameters each events passes, and of course you can also have multiple events. Which of those two is appropriate for yoyur case, that's for you to decide. Also, "inherit" does not really make sense in the context of notifications. What would it mean? – Deduplicator Nov 10 '14 at 01:56
  • I know, but creating a method for each situation like: `notifyCatDead` and `notifyCatSick` and a method to each new state will be painful somehow because I will need to implement in every subject and observer. - Using an object that holds that state and the listener/observer tries to know the state of that object seems to be less painful but seems to broke rules as well because to know the state of the object I will need to use switch or if. – yayuj Nov 10 '14 at 02:00
  • What about something more along the lines of `notifyState(enum state)`? – Deduplicator Nov 10 '14 at 02:01
  • But as I said, the notifications has states and can have methods as well that the observers/listeners can use. I think that I found something that can be useful but I still don't know if is the best way: Using `dynamic_cast` and compare if the type of notification is the one I want in the observer/listener. For example: I have a listener called `CatDiedListener`, inside of the `receive(AnimalEvent* notification)` I can use `if (notification = dynamic_cast(notification))` and know if the base class is the one I want. - What you think about that approach? – yayuj Nov 10 '14 at 02:12
  • @MrAlmighty: I think you are forcing too much OOP and especially inheritance. Might work anyway. – Deduplicator Nov 10 '14 at 02:34
  • @Deduplicator - Actually I'm trying to avoid future problems the maximum I can, OO is not only programming but analysis and project right? That is the reason I search for the best way for doing something, not only programmatic thinking but usability, performance, safety and other things that makes the code better, after all we don't code for machines but programmers. - I totally agree with you that I might be forcing but that is the reason I came here, several people that thinks different might help. - You did somehow and thank you. I will try to choose/find the best way to do. – yayuj Nov 10 '14 at 02:45
2

Variadic templates in the new standard is a perfect way to solve your problem. You can take a look on Observer pattern implementation with templates here: https://github.com/fnz/ObserverManager

Victor Komarov
  • 319
  • 4
  • 6
1

Listeners need to know the signature they will be called with pretty fundamentally. Bundling sets of messages is useful, but only sometimes worth it. Consider this interface:

template<class...Args>
struct broadcaster {
  template<class F> // F:Args...->vpid
  std::shared_ptr<void> attach( F&& f );
  size_t operator()(Args... args)const;
};

now listeners need only pass an invokable to the broadcaster, then store a shared ptr while they want to listen. When the shared ptr expires, tue message stops being sent.

The sender invokes the broadcaster with the arguments.

template<class...Args>
struct broadcaster {
  template<class F> // F:Args...->vpid
  std::shared_ptr<void> attach( F&& f ){
    auto x = std::make_shared<std::function<void(Args...)>>(std::forward<F>(f)));
    funcs.push_back(x);
    return x;
  }
  size_t operator()(Args... args)const{
    funcs.erase(
      std::remove_if(begin(funcs),end(funcs),
        [](auto&&f){return f.expired();}
      )
      ,end(funcs)
    );
    aito fs=funcs;
    for(auto&&f:fs){
      if(auto f_=f.lock())
        f_(args...);
    }
  }
public:
  mutable std::vector<std::weak_ptr<std::function<void(Args...)>>> funcs;
};

which is a mouthful.

It does mean your two methods are decoupled. But they can be connected with a simple lambda. Store a std::vector<std::shared_ptr<void>> in the listening classes to handle l8fetime management issues.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Two points: 1. Why do you insist on creating a new shared_ptr? Better allow passing one which already has the wanted life-time anyway. 2. There's no need to iterate over the listeners twice on calling, once is enough. That also allows you to ask the listener whether it shall ever be called again, if you want to. – Deduplicator Nov 10 '14 at 00:16
  • @Yakk - Read this: https://stackoverflow.com/questions/26833920/observer-pattern-with-different-notifications/26834487#comment42236987_26834487 – yayuj Nov 10 '14 at 01:56
  • @dedup the above allows listeners to be added when broadcasting without order issues (or other changes to the list). In multithreaded code, you release the lock before iterating over the copy of the listeners. The list is likely to be short anyhow (10s or 100s not 1000s or millions). As for a pre existing smart ptr, a slight change will allow that. – Yakk - Adam Nevraumont Nov 10 '14 at 02:12