Search code examples
c++templatesobserver-patternvirtual-functions

Way around templated virtual function for events


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?


Solution

  • 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);
            }
        }
    };