Шаблон наблюдателя с различными уведомлениями

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

Обычно в реализациях шаблонов наблюдателей вы можете видеть только один метод с именем notify где он уведомляет наблюдателей о том, что что-то произошло, и имеет своего рода инверсию, когда наблюдатель держит указатель субъекта и запрашивает у субъекта что-то, когда его уведомляют.

Я реализую это немного по-другому, когда субъект прикрепляет наблюдателей и уведомляет их всех без необходимости держать указатель субъекта внутри наблюдателей. Например:

#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;
}

Единственное, что меня беспокоит, так это то, что я не нарушаю какой-либо принцип проектирования, такой как Open и Closed или что-то подобное, а также если мне нужно добавить новое уведомление, которое мне нужно реализовать во всех других классах. (что больно — представьте 10 наблюдателей или даже больше).

Я думал о том, чтобы сделать это по-другому, создать только один интерфейс, а затем я могу унаследовать его, создавая другие уведомления, но есть проблема, как наблюдатели могут определить, что представляет собой каждый другой тип уведомлений?

Пример:

#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;
}

Реализация является лишь примером, я знаю, что могу использовать умные указатели, удалять необработанные указатели и делать что-то лучше, но это ПРОСТО пример реализации.

Проблема с этим новым подходом заключается в том, что мне нужно знать, NotifierAPI для того, чтобы использовать его внутри Observer, чтобы вызвать getInt,

Как я могу это сделать, каков наилучший способ сделать это? Как я могу отправлять разные уведомления наблюдателям?

2

Решение

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

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

Создавать как

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

Или с любым другим контейнером вызовов.

Полезным изменением будет использование контейнера std::tuple<tag, callable>где тег должен быть указан при добавлении и может использоваться для удаления слушателя.

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

Последний создается с типом функтора, как std::function<...> или указатель на функцию.

2

Другие решения

Шаблоны Variadic в новом стандарте — отличный способ решить вашу проблему. Вы можете взглянуть на реализацию шаблона Observer с шаблонами здесь: https://github.com/fnz/ObserverManager

2

Слушатели должны знать, с какой подписью они будут называться. Объединение наборов сообщений полезно, но только иногда оно того стоит. Рассмотрим этот интерфейс:

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

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

Отправитель вызывает вещателя с аргументами.

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

который глоток

Это означает, что ваши два метода разделены. Но они могут быть связаны с простой лямбдой. Магазин std::vector<std::shared_ptr<void>> в классах прослушивания для решения проблем управления временем.

1
По вопросам рекламы [email protected]