Полиморфизм
Система ивентов (событий)
Заголовок раздела «Система ивентов (событий)»- Реализуйте простую систему ивентов (событий). Интерфейс должен быть следующий:
-
Структура
EventSystem, которая будет хранить список обработчиков (event handler) для каждого типа события. -
Структура
EventHandler, которая будет хранить идентификатор хандлера (какое-то число), и саму функцию обработчик. -
Определите тип функции обработчика
EventHandlerFuncчерезusing. Это может быть один из следующих, выбирайте сами, что хотите использовать:-
Указатель на функцию, но в этом случае знайте, что не сможете сохранить с ней контекст.
-
std::function. По моему самый простой и при том гибкий вариант. -
EventHandlerAbstractBase*, гдеEventHandlerAbstractBase- абстрактный базовый класс, который определяет виртуальную функциюhandleEvent(EventInfo* event). Это позволит вам сохранять контекст в наследниках этого класса. В этом случае не забудьте удалять хандлер при отписке.class EventHandlerAbstractBase{public:virtual void handleEvent(EventInfo* event) = 0;};class MyHandler : public HandlerAbstractBase{MyContext* context;public:void handleEvent(EventInfo* event) override{// ...}};
-
-
Структура
EventHandle, которая будет хранить идентификатор события (какое-то число). -
Структура
EventInfo, которая будет передаваться обработчикам событий. Тут в ваших руках какие именно данные передавать, и как:-
Можете использовать type erasure, храня
void*на какие-то дополнительные данные, и кастите в нужный тип в обработчиках.struct EventInfo{void* context;// ...};// ...void handleEvent(EventInfo* event){auto context = static_cast<MyContext*>(event->context);// ...} -
Можете взять просто какие-то фиксированные, обобщенные данные, но тогда вероятно не сможете передавать более интересный контекст при обработке ивента.
struct EventInfo{int someData1;int someData2;};void handleEvent(EventInfo* event){std::cout << event->someData1 << std::endl;} -
Можете добавить
templateпараметр этой структуре для типа payload-а. Это наиболее сложный вариант, который к тому же заставит вас добалять шаблоны в самуEventSystem, но он же и наиболее гибкий.template<typename Payload>struct EventInfo{Payload payload;// ... some more generic info};template<typename Payload>void dispatchEvent(EventSystem& eventSystem,size_t eventId,EventInfo<Payload>* event){std::span<EventHandler<Payload>> handlers = getEventHandlersById<Payload>(eventSystem, eventId);for (const auto& handler : handlers){handler.func(event);}}
-
-
Функция
EventHandle createEvent(EventSystem& eventSystem)илиEventHandle<T> createEvent(EventSystem& eventSystem).Возвращает идентификатор события, который можно использовать для последующей подписки обработчиков. Каждый последующий идентификатор пусть будет на 1 больше предыдущего.
Для упрощения дебаггинга, можете сохранять имя события в
EventSystem. Можете также использоватьstd::unordered_mapсо строками, заданными пользователем, в качестве индентификаторов событий. -
Функция
EventHandlerHandle subscribe(EventSystem& eventSystem, EventHandle eventId, EventHandlerFunc&& func);, которая добавляет обработчик на заданное событие. Возвращает идентификатор обработчика, который можно использовать для отписки в стрктуреEventHandlerHandle. Каждый последующий идентификатор пусть будет на 1 больше предыдущего. -
Функция
bool unsubscribe(EventSystem& eventSystem, EventHandle eventId, EventHandlerHandle handle);, которая удаляет обработчик с заданным идентификатором, с заданного ивента (можете предположить, что хандлеры не могут отписывать другие хандлеры). Возвращаетсяfalse, если хандлер не был найден. -
Функция
void dispatch(EventSystem& eventSystem, EventHandle eventId, EventInfo* event);, которая вызывает все обработчики события с заданным идентификатором.
-
Создайте меню, которое иллюстрирует использование. Меню должно иметь следующие опции:
-
Создать новое событие, изначально без подписок. Если выбрали вариант с
template, пользователю должен быть предоставлен доступ к выбору типаPayloadиз одного из известных типов (как минимум 2). -
Подписать один из обработчиков на определенное событие. Пользователю должен быть предоставлен выбор одной из нескольких функций, определенных заранее в программе (минимум 6). Перед привязкой функции, если она ожидает дополнительного контекста (например, функция “добавить N к числу в payload” будет ожидать число N), предоставьте пользователю возможность ввести этот контекст.
-
Отписать обработчик, предоставив идентификатор события и обработчика.
-
Напечатать на экран идентификаторы всех событий и обработчиков в них.
-
Вызвать событие, предоставив идентификатор события и по необходимости данные для обработчиков.
-
-
Возможные идеи для ивентов / обработчиков:
-
Ивент применения фильтров при выборе цвета. Обработчики которые комбинируют / переопределяют / преобразуют цвет, переданный в контексте события. По итогу печатается выбранный цвет после обработки.
-
Математический pipeline для чисел. Обработчики которые добавляют к, умножают, берут корень из числа, переданного в контексте события.
-
Ивенты в контексте игры. Можете представить, что один из ивентов, вызывается при нажатии игроком на кнопку. Эффект от этого может быть денежный бонус игроку со случайным шансом, взрыв динамита и смерть игрока, увеличение очков, и т.д.
Принимаются любые идеи.
Не используйте глобальные переменные!.