Перейти к содержимому

Полиморфизм

  1. Реализуйте простую систему ивентов (событий). Интерфейс должен быть следующий:
  • Структура 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);, которая вызывает все обработчики события с заданным идентификатором.

  1. Создайте меню, которое иллюстрирует использование. Меню должно иметь следующие опции:

    • Создать новое событие, изначально без подписок. Если выбрали вариант с template, пользователю должен быть предоставлен доступ к выбору типа Payload из одного из известных типов (как минимум 2).

    • Подписать один из обработчиков на определенное событие. Пользователю должен быть предоставлен выбор одной из нескольких функций, определенных заранее в программе (минимум 6). Перед привязкой функции, если она ожидает дополнительного контекста (например, функция “добавить N к числу в payload” будет ожидать число N), предоставьте пользователю возможность ввести этот контекст.

    • Отписать обработчик, предоставив идентификатор события и обработчика.

    • Напечатать на экран идентификаторы всех событий и обработчиков в них.

    • Вызвать событие, предоставив идентификатор события и по необходимости данные для обработчиков.

  2. Возможные идеи для ивентов / обработчиков:

  • Ивент применения фильтров при выборе цвета. Обработчики которые комбинируют / переопределяют / преобразуют цвет, переданный в контексте события. По итогу печатается выбранный цвет после обработки.

  • Математический pipeline для чисел. Обработчики которые добавляют к, умножают, берут корень из числа, переданного в контексте события.

  • Ивенты в контексте игры. Можете представить, что один из ивентов, вызывается при нажатии игроком на кнопку. Эффект от этого может быть денежный бонус игроку со случайным шансом, взрыв динамита и смерть игрока, увеличение очков, и т.д.

Принимаются любые идеи.

Не используйте глобальные переменные!.