Я пишу Window
класс, который распространяет различные типы событий, перечисленных в
enum Event {WINDOW_ClOSE=0x1, WINDOW_REDRAW=0x2, MOUSE_MOVE=0x4, ...};
к объектам, которые зарегистрировались для уведомления с окном. Для каждого типа события у меня есть абстрактный класс, который любой объект должен расширять, чтобы разрешить уведомление. Чтобы реагировать, скажем, на MOUSE_MOVE
событие, мой объект будет наследоваться от MouseMoveListener
, который имеет process_mouse_move_event()
метод, который вызывается Window
, Прослушивание многих событий может быть объединено путем расширения нескольких этих классов, которые все наследуются от EventListener
Базовый класс. Чтобы зарегистрировать объект, я бы позвонил
void Window::register(EventListener* object, int EventTypes)
{
if(EventTypes&WINDOW_CLOSE)
/* dynamic_cast object to WindowCloseListener*, add to internal list of all
WindowCloseListeners if the cast works, else raise error */
if(EventTypes&MOUSE_MOVE)
/* dynamic_cast object to MouseMoveListener*, add to internal list of all
MouseMoveListeners if the cast works, else raise error */
...
}
Это прекрасно работает, но моя хватка в том, что EventListener
является полностью пустой, и это кажется мне вонючим кодом. Я знаю, что мог избежать этого, удалив EventListener
в целом и имея отдельный Window::register
для каждого типа событий, но я чувствую, что это может взорвать мой интерфейс без необходимости (особенно потому, что методы, кроме register
может возникнуть с той же проблемой). Поэтому я думаю, что я ищу ответы, которые либо говорят:
«Вы можете продолжать делать это так, как вы делаете, потому что …» или
«Ввести отдельный Window::register
методы в любом случае, потому что … «или, конечно,
«Вы все делаете неправильно, вы должны …».
РЕДАКТИРОВАТЬ:
По ссылке в комментарии Igors: То, что я делаю выше, работает, только если есть хотя бы один виртуальный член в EventListener
например, виртуальный деструктор, поэтому класс технически не полностью пуст.
РЕДАКТИРОВАТЬ 2:
Я преждевременно принял решение н.м. как один из типов «я все делаю неправильно». Однако это второй тип. Даже если я могу позвонить EventListener->register(Window&)
полиморфно, Window
необходимо реализовать интерфейс с высокой избыточностью (с точки зрения заявленных методов), который позволяет EventListeners
зарегистрироваться для выборочного уведомления. Это эквивалентно моему альтернативному решению, описанному выше, только с дополнительным введением EventListener
класс без уважительной причины. В заключение, канонический ответ выглядит так:
Не делай dynamic_cast
+ пустой базовый класс, просто чтобы не объявлять много похожих функций, это повредит вам при поддержке кода позже. Напишите много функций.
РЕДАКТИРОВАТЬ 3:
Я нашел решение (с использованием шаблонов), которое меня устраивает. Он больше не использует пустой базовый класс и не демонстрирует проблему обслуживания, указанную в n.m.
object->registerWindow (this, EventTypes);
Конечно, вам нужно реализовать registerWindow
для всех EventListener
наследники. Пусть они проверяют типы событий, которые имеют отношение к их.
ОБНОВИТЬ
Если это означает, что вам нужно переделать свой код, то вам нужно переделать свой код. Почему это так? Так как dynamic_cast
является не правильный способ сделать типы включения. Это неправильный способ, потому что каждый раз, когда вы добавляете класс в вашу иерархию, вам нужно перейти и, возможно, обновить все переключает динамическое приведение в ваш старый код. Это становится очень запутанным и неосуществимым очень быстро, и это как раз та причина, по которой были изобретены виртуальные функции.
Если вы включаете типы включения с помощью виртуальных функций, каждый раз, когда вы меняете иерархию, вы должны делать … ничего. Механизм виртуального вызова позаботится о ваших изменениях.
Это то, что я в итоге сделал:
template <int EventType> void register_(EventListener<EventType> Listener)
{
// do stuff with Listener, using more templates
};
Оказалось, что статический полиморфизм лучше подходит для моих нужд — я просто хотел избежать написания
register_mouse_motion_event(...)
register_keyboard_event(...)
и так далее. Этот подход также хорошо устраняет необходимость в пустом базовом классе.