Создание & quot; Publisher- & gt; Dispatcher- & gt; Subscriber & quot; система событий образца?

Изменить: TL; DR

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

То есть: EventChild выходит из EventBase. Функция с подписью

<void (EventChild&)>

не будет вписываться в переменную типа

std::function<void(EventBase&)>

Как мне хранить подобные функции, зная, что пользователь не должен изменять класс, в котором он хранится, каждый раз, когда он создает новое событие, выходящее из нашего класса EventBase?

Примечание: мне ранее сказали, что я могу использовать dynamic_cast для достижения этой цели. Я пытался сделать именно это, но это не сработало. Я думаю, чтобы это работало, мне пришлось бы как-то использовать указатели, но я достаточно новичок в C ++, чтобы не знать, как это сделать. Может быть, это должно быть отправной точкой?

Одна из проблем с указателями динамического приведения: «Я могу преобразовать указатель типа:

(Subbscriber*)(getDemoEvent(EventDemo&)

печатать:

void(EventBase&)

Или что-то вдоль этих линий. (сейчас не на моем компьютере, чтобы попробовать)
Я полагаю, что это проблема, ограниченная функциями-членами.


Недавно я опубликовал здесь вопрос с намерением решить проблему для системы событий C ++, основанной на шаблоне «Publisher-> Dispatcher-> Subscriber». Я не знаю точного названия этого паттерна, но слышу, что это вариант паттерна Observer с добавленным «посредником».

Я пытался заставить эту систему работать некоторое время, и я полностью застрял. В комментариях к предыдущему вопросу было высказано предположение, что то, что я пытался выполнить, неверно в моей программе. Это очень вероятно, так как я исследовал другие системы событий, которые были близки к тому, что я имею после попытки изменить их для использования, для которого они были предназначены. Поэтому я решил, что опишу, что мне нужно, и задам более общий вопрос: «Как бы вы пошли на структурирование и создание этого?»

Итак, вот мое общее представление о том, как должна быть устроена система и как она должна работать в базовом примере:

Начнем с идеи 5 разных файлов (плюс заголовки и, возможно, несколько подклассов):

main.cpp
dispatcher.cpp
publisher.cpp
subscriber.cpp
eventbase.cpp

Издатели и подписчики могут быть чем угодно, и они служат здесь только примером.
Первым делом было бы создать экземпляр нашего класса Dispatcher.
После этого мы создаем экземпляры наших классов издателя / подписчика. Эти 2 класса могут быть частью одного и того же файла, разных файлов, кратных каждому или вообще не быть классами, а могут быть просто свободными функциями. Для простоты и тестирования это два отдельных класса, которые ничего не знают друг о друге.
Когда эти 2 класса созданы, им нужно передать ссылку или указатель на наш экземпляр диспетчера.
Это достаточно просто. Теперь давайте перейдем к тому, как вы должны использовать систему.

Пользователь системы должен иметь возможность создавать класс, который наследуется от нашего класса EventBase. В идеале не должно быть никаких требований к переменным или функциям для переопределения из базового класса.
Допустим, мы создали новый класс событий с именем EventDemo с общедоступным const char * demoString = «Я — демо-событие» ;.

Из нашего класса подписчиков мы должны быть в состоянии сообщить нашему диспетчеру, что хотим прослушивать и получать некоторые события. Синтаксис для этого должен быть максимально простым.
Давайте создадим функцию-член в нашем подписчике, которая выглядит следующим образом:

void Subscriber::getDemoEvent(const EventDemo &ev) {
std::cout << ev.demoString;
}

Теперь нам нужен способ привязать эту функцию-член к нашему диспетчеру. Вероятно, мы должны сделать это в нашем конструкторе. Допустим, что ссылка на нашего диспетчера, которую мы передали нашему подписчику при создании, называется просто «диспетчер».
Синтаксис для подписки на событие должен выглядеть примерно так:

dispatcher->subscribe("EventToSubTo", &getDemoEvent);

Теперь, когда мы в классе пытаемся передать функцию-член, это, вероятно, невозможно, но это будет работать для свободных функций.
Для функций-членов нам, вероятно, понадобится переопределение, которое выглядит следующим образом:

dispatcher->subscribe("EventToSubTo", &Subscriber::getDemoEvent, this);

Мы используем «это», так как мы находимся внутри конструктора подписчиков. В противном случае мы могли бы использовать ссылку на нашего подписчика.
Обратите внимание, что я просто использую строку (или const char * в терминах c ++) в качестве моего «ключа события». Это сделано специально, чтобы вы могли использовать один и тот же «тип» события для нескольких событий. И.Е .: EventDemo () можно отправить на клавиши «Event1» и «Event2».

Теперь нам нужно отправить событие. Это можно сделать везде, где у нас есть ссылка на нашего диспетчера. В этом случае где-то в нашем классе издателя.
Синтаксис должен выглядеть примерно так, чтобы отправить наш EventDemo:

dispatcher->emit("EventToSubTo", EventDemo());

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

dispatcher->emit("EventToSubTo", EventDemo(42));

или же

dispatcher->emit("EventToSubTo", EventDemo<float>(3.14159f));

Пользователь может создать функцию-член для извлечения данных.

Итак, все это должно показаться довольно простым, и, на самом деле, так и есть, за исключением одной маленькой ошибки. Уже существуют системы, которые хранят функции на карте с типом.
И в этом заключается проблема…
Мы можем хранить наши функции слушателя, если они принимают тип EventBase в качестве аргумента. Затем мы должны были бы набрать приведение этого аргумента к типу события, которое мы ищем. Это не слишком сложно, но не в этом дело. Дело в том, может ли это быть лучше.
Другим решением, которое было предложено ранее, была идея иметь отдельную карту или вектор для каждого типа события. Это тоже неплохо, но потребует, чтобы пользователь либо изменил класс диспетчера (что было бы трудно сделать, когда это библиотека), либо каким-то образом сказал бы диспетчеру «создать этот набор карт» во время компиляции. Это также сделало бы событие шаблонным кошмаром.

Итак, чрезмерно обобщенный вопрос: как мы это делаем?

Это было, вероятно, очень длинное объяснение чего-то простого, но, возможно, кто-то придет, не зная об этом.

Мне очень интересно услышать мысли по этому поводу. Основная идея заключается в том, что я не хочу, чтобы два коммуникатора (издатель и подписчик) знали что-либо друг о друге (без указателей или ссылок), но при этом были в состоянии передавать произвольные данные от одного к другому. Большинство реализаций, которые я видел (сигналы и слоты), требуют некоторой ссылки друг на друга. Создание интерфейса «среднего человека» кажется гораздо более гибким.

Спасибо за ваше время.


Для ссылки на мой последний вопрос с примерами кода того, что я имею до сих пор:
Сохраните функцию с произвольными аргументами и заполнителями в классе и вызовите ее позже

У меня есть больше образцов, которые я мог бы опубликовать, но я думаю, что весьма вероятно, что структура системы должна будет измениться. В ожидании услышать мысли!

1

Решение

Задача ещё не решена.

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

Других решений пока нет …

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector