2

I'm basically trying to implement a generic observer pattern.

class Observer {
public:
    template <class T>
    virtual void OnEvent(const EventHandle& h, T& affectedItem) = 0;
};

class Subject {
public:
    void AddObserver(Observer* o) {
        observers.Add(o);
    }
    void RemoveObserver(Observer* o) {
        observers.Remove(o);
    }
    template <class T>
    void Notify(const EventHandle& h, T& affectedItem) {
        for (Observer* o : observers) {
            o->OnEvent(h, affectedItem);
        }
    }
private:
    set<Observer*> observers;
};

In Observer::OnEvent(), I'd like to get the item that had been affected in the event (say I just added something to an inventory and need to reflect the addition in the GUI--I would call Subject::Notify(itemAddedEvent, newItem)). I know I'll need some refactoring/redesign of classes but I'm stuck on how. What are some ways of getting around this?

idlackage
  • 2,715
  • 8
  • 31
  • 52
  • Does http://stackoverflow.com/a/32081759/1774667 solve your problem? Basically, a "subject" or "broadcaster" is templated on its message. Recievers are also typed on the message (in my case, recievers are just invokable objects, to allow easy use of lambdas, and removing an observer consists of destroying the returned `shared_ptr` instead of going back to the broadcaster, because I find ensuring broadcasters outlive receivers to be a major source of bugs) – Yakk - Adam Nevraumont Aug 26 '15 at 18:25

2 Answers2

1

Unfortunately, as you might have seen at compilation time, it's not allowed to have templates for virtual functions.

As you have choosen virtual functions, I guess that you intend to use somehow polymorphism. If this is the case you could perfectly well use a tempalteless implementation, using inheritance . The only constraint is that your different actors inherit from your generic classes. But his is not a strong constraint thanks to possible multiple inheritance.

The observer could look like (I've use Subject for the affected item, but you could use a third class):

class Subject; 
class Observer {
public:
    virtual void OnEvent(const EventHandle& h, Subject& affectedItem) = 0;
    virtual ~Observer() {}  // virtual function ? => better foresee a virtual destructor
};

The obervee would remain almost unchanged:

class Subject {
public:
    ...
    void Notify(const EventHandle& h, Subject& affectedItem) {
        for (Observer* o : observers) {
            o->OnEvent(h, affectedItem);
        }
    }
private:
    set<Observer*> observers;
};

The using class would then be like:

struct ConcreteObserverA : Observer {
    void OnEvent(const EventHandle& h, Subject& affectedItem) override {
        cout<<"A:"<<&affectedItem<<endl;
    }
struct ConcreteSubjectSA : Subject { };

Here a live demo.

Christophe
  • 68,716
  • 7
  • 72
  • 138
  • Thanks, I am pretty much using inheritance currently but the problem is the `affectedItem` parameter--I don't want to pass in `Subject` or a third class because it may not be an object, it may be an int or bool or some other primitive data type. Going with the inventory example: if I removed an item at index `i`, as the GUI/Observer I'd want to know exactly what `i` is. – idlackage Aug 26 '15 at 19:14
0

Really needed a quick solution so came up with this. I realize that this is terribly hacky, that I'm probably destroying the principals of programming and should throw my computer out the window to atone for the sin of even suggesting this as a solution, etc., but it does exactly what I want and is easy to understand so for now I'm going to keep with it until something better comes up.

Basically I'm just wrapping (possibly primitive) data inside an object:

struct ParamBase {};

template <class T>
struct ConcreteParam : ParamBase {
    T data;
    ConcreteParam(T t) : data(t) {}
};

class Observer {
public:
    virtual void OnEvent(const EventHandle& h, const ParamBase& affectedItem) = 0;
protected:
    template <class T>
    T getParamData(const ParamBase& p) {
        const ParamBase* pb = &p;
        return ((ConcreteParam<T>*)pb)->data;
    }
};

class Subject {
public:
    // Add/Remove functions stripped
    template <class T>
    void Notify(const EventHandle& h, T affectedItem) {
        for (Observer* o : observers) {
            o->OnEvent(h, ConcreteParam<T>(affectedItem));
        }
    }
};

Example usage:

class InventoryUI : public Observer {
public:
    virtual void OnEvent(const EventHandle& h, const ParamBase& affectedItem) {
        if (h == itemRemovedEvent) {
            int index = getParamData<int>(affectedItem);
            removeItemAt(index);
        }
        else if (h == itemAddedEvent) {
            Item* item = getParamData<Item*>(affectedItem);
            addItemToGrid(item);
        }
    }
};
idlackage
  • 2,715
  • 8
  • 31
  • 52