Хороший вопрос! Прежде чем перейти к конкретным вопросам, которые вы задали, я скажу: не стоит недооценивать силу простоты. Тенпн прав. Имейте в виду, что все, что вы пытаетесь сделать с помощью этих подходов, - это найти элегантный способ отложить вызов функции или отделить вызывающего абонента от вызываемого. Я могу рекомендовать сопрограммы как удивительно интуитивный способ облегчить некоторые из этих проблем, но это немного не по теме. Иногда вам лучше просто вызвать функцию и жить с тем фактом, что сущность A связана непосредственно с сущностью B. См. YAGNI.
Тем не менее, я использовал и был доволен моделью сигнал / слот в сочетании с простой передачей сообщений. Я использовал его в C ++ и Lua для довольно успешного названия для iPhone с очень плотным графиком.
В случае с сигналом / слотом, если я хочу, чтобы объект A делал что-то в ответ на то, что сделал объект B (например, открывает дверь, когда что-то умирает), я мог бы попросить объект A подписаться непосредственно на событие смерти объекта B. Или, возможно, субъект А подписывался на каждую группу объектов, увеличивал счетчик на каждое запущенное событие и открывал дверь после того, как N из них умерло. Кроме того, «группа объектов» и «N из них» обычно определяются конструктором в данных уровня. (Кроме того, это одна область, где сопрограммы могут действительно сиять, например, WaitForMultiple («Умирающий», entA, entB, entC); door.Unlock ();)
Но это может быть обременительным, когда дело доходит до реакций, которые тесно связаны с кодом C ++, или по сути эфемерных игровых событий: нанесение урона, перезагрузка оружия, отладка, управляемая игроками обратная связь с ИИ на основе определения местоположения. Именно здесь передача сообщений может заполнить пробелы. По сути, это сводится к чему-то вроде: «Скажите всем сущностям в этой области нанести урон через 3 секунды» или «Когда вы закончите физику, чтобы выяснить, кого я застрелил, скажите им запустить эту функцию сценария». Трудно понять, как это сделать, используя публикацию / подписку или сигнал / слот.
Это легко может быть излишним (по сравнению с примером tenpn). Это также может быть неэффективным раздутием, если у вас много действий. Но, несмотря на свои недостатки, этот подход «сообщения и события» очень хорошо сочетается со скриптовым игровым кодом (например, в Lua). Код сценария может определять свои собственные сообщения и события и реагировать на них, не обращая внимания на код C ++. И код сценария может легко отправлять сообщения, которые запускают код C ++, например, изменение уровней, воспроизведение звуков или даже просто разрешение оружию установить, какой урон наносит сообщение TakeDamage. Это сэкономило мне массу времени, потому что мне не приходилось постоянно дурачиться с Луабиндом. И это позволило мне хранить весь мой luabind-код в одном месте, потому что его было немного. Когда правильно соединены,
Кроме того, мой опыт использования варианта № 2 заключается в том, что вам лучше обрабатывать его как событие в другом направлении. Вместо того, чтобы спрашивать, каково состояние объекта, запускайте событие / отправляйте сообщение всякий раз, когда здоровье вносит существенные изменения.
Что касается интерфейсов, между прочим, я реализовал три класса для реализации всего этого: EventHost, EventClient и MessageClient. EventHosts создает слоты, EventClients подписываются / подключаются к ним, а MessageClients связывают делегата с сообщением. Обратите внимание, что целевой объект делегата MessageClient не обязательно должен быть тем же объектом, который владеет ассоциацией. Другими словами, MessageClients могут существовать исключительно для пересылки сообщений другим объектам. FWIW, метафора хост / клиент является неуместной. Источник / Раковина могли бы быть лучшими понятиями.
Извините, я там немного побродил. Это мой первый ответ :) Надеюсь, это имело смысл.