Универсальная реализация паттерна Observer

в 18:44, , рубрики: Песочница, метки: ,

Понадобилось мне, чтобы каждый класс мог стать субъектом (subject) и оповещать своих наблюдателей (observers) любыми типами данных.

Чтобы вечно не использовать наследование для разных типов данных, я написал универсальный шаблонный класс.

Для начала определим интерфейсы наблюдателя и субъекта.

ASObserver.h

template <class T>

class ASObserver{

public:
    virtual     ~ASObserver(){}
    virtual void onNotify(T *data) = 0;

};


ASSubject.h

#include "ASObserver.h"
#include <vector>

using std::vector;
using std::remove;

// ObserverClass — тот, кто наблюдает
// ObserverDataClass — тип данных, которым мы будем уведомлять ObserverClass
// с помощью метода notify

template <class ObserverClass, class ObserverDataClass>
class ASSubject{

public:
    virtual void    addObserver(ASObserver<ObserverDataClass> *observer) = 0;
    virtual void    removeObserver(ASObserver<ObserverDataClass> *observer){};

protected:
    virtual void    notify(ObserverDataClass *data) = 0;
};

Теперь реализуем в своих классах методы интерфейса. В файле ASConcreteSubject.tpp реализация шаблонных методов (для удобства, я просто разделил объявление и реализацию).

ASConcreteSubject.h

#include "ASSubject.h"
#include "ASObserver.h"

using std::vector;
using std::remove;

template <class ObserverClass, class ObserverDataClass>
class ASConcreteSubject : public ASSubject<ObserverClass, ObserverDataClass>{

public:
    void    addObserver(ASObserver<ObserverDataClass> *observer);
    void    removeObserver(ASObserver<ObserverDataClass> *observer);

protected:
    void    notify(ObserverDataClass *data);
private:
    vector<ASObserver<ObserverDataClass> *> observers;
};

#include "ASConcreteSubject.tpp"

ASConcreteSubject.tpp

#include "ASConcreteSubject.h"

template <class ObserverClass,class ObserverDataClass>

void ASConcreteSubject<ObserverClass, ObserverDataClass>::addObserver(ASObserver<ObserverDataClass> *observer){

observers.push_back(observer);

}

template <class ObserverClass,class ObserverDataClass>

void ASConcreteSubject<ObserverClass, ObserverDataClass>::removeObserver(ASObserver<ObserverDataClass> *observer) {
observers.erase(remove(observers.begin(), observers.end(), observer), observers.end());
}

template <class ObserverClass,class ObserverDataClass>
void ASConcreteSubject<ObserverClass, ObserverDataClass>::notify(ObserverDataClass *data){
for( auto &observer : observers ){
observer->onNotify(data);
}
}

А теперь самое интересное: а как же использовать этот код?

main.cpp

struct mydata_t{ int code; }

class TestSubject : public ASConcreteSubject<Test, mydata_t>{

public:

void onKeyPressed(){
    mydata.code = 5;
    notify(mydata);
}

private:
mydata_t mydata;

};

class TestObserver : public ASObserver<mydata_t>{

public:
TestObserver(){ testSubject.addObserver(this); }

private:
TestSubject testSubject;
};

Таким образом можно, например, реализовать систему уведомлений, вроде keyboard events.

По нажатию клавиш уведомлять своих наблюдателей, при том, наблюдателем может быть любой класс, а оповещать любыми данными.

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js