Search code examples
c++design-patternsobserver-patterndesign-principles

Observer pattern with different notifications


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?


Solution

  • 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.