Правильный дизайн для передачи обобщений в сообщениях слушателям событий - PullRequest
0 голосов
/ 07 марта 2019

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

У меня возникли проблемы с пониманием того, как передавать универсальные аргументы слушателям событий (например, функциям обратного вызова) без нарушения принципов ООП и полиморфизма.

Для целей этого вопроса предположим, что реализация написана на C ++.

Допустим, у меня есть animal класс с двумя детьми, cat и dog.

У меня также есть объект события doorbell, который должен вызвать от dogs до speak(), но cats не отвечает.

Однако все мои animals хранятся полиморфно. Я не могу просто позвонить speak() на каждое животное, так как кошки не должны отвечать.

Одно (плохое) решение - использовать динамическое приведение для проверки каждого животного и посмотреть, относится ли оно к типу dog.. Если да, я вызываю его функцию speak. Если нет, то нет. Проблема здесь в том, что он нарушает назначение полиморфизма и виртуальных функций (не нужно знать информацию о дочерних классах, а только о родительском интерфейсе).

Для лучшего решения я мог бы выбрать использование системы, управляемой событиями. У меня был бы event_manager,, и каждая собака регистрировалась как слушатель события doorbell (возможно, по некоторому идентификатору события, имени события и т. Д.). Возможно, каждая собака регистрирует свою функцию обратного вызова вместе с событием.

Затем, когда звонит doorbell, он сообщает event_manager, чтобы он вызывал функции обратного вызова каждого слушателя, зарегистрированного для события doorbell.

Это намного лучше, но что, если информация о цели (doorbell_ring) должна быть передана собакам? Например, как громко это прозвучало? Если было тихо, возможно, dogs не отвечают, исходя из их слуховых способностей. Возможно, если он очень громкий, dogs реагируют более агрессивно.

Единственное решение - передать целевой doorbell в функцию обратного вызова обработчика событий (dog).

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

Единственное решение, которое я могу придумать, это передать цели как void pointers и явно понизить рейтинг до определенного типа цели (например, собаки в этой функции обратного вызова будут понижать цель до типа doorbell_ring*).

Хотя это, безусловно, сработает, разве это не то же самое, что и самое первое решение, в котором мы проверяем, является ли животное типа dog посредством динамического приведения, и, если да, вызываем его ответ? Представьте, что вместо использования динамического приведения мы даем каждому животному идентификатор типа и просто проверяем каждый идентификатор животного. Теперь мы просто приводим животное к собаке на основе его идентификатора. В управляемой событиями системе мы просто приводим цель к определенному подтипу на основе идентификатора события, которое вызвало обратный вызов.

В общем, разве это не имеет те же проблемы с дизайном? Мы просто используем обобщенные указатели void и понижаем значение до правильного типа на основе некоторого идентификатора типа, а не понижаем значение с класса animal до класса dog.

Есть ли лучший шаблон дизайна?

...