Лучший шаблон проектирования для прослушивателей событий

#c #design-patterns #mfc

#c #шаблоны проектирования #mfc

Вопрос:

Используя C и MFC, я создал класс, который упрощает добавление функций перетаскивания к CWnd объекту. На самом деле в этом нет ничего особенного. В настоящее время он используется следующим образом:

  1. Создайте объект CDropListener
  2. Вызовите метод для объекта CDropListener, указав, на какое расширение файла вы хотите, чтобы он реагировал, и указатель на функцию, которую следует вызывать при удалении файла
  3. Зарегистрируйте это с CWnd помощью объекта
  4. Удалите объект CDropListener при уничтожении окна
  5. Повторите все описанные выше шаги, если вам нужны разные расширения файлов для разных CWnd

Немного сложно создавать переменную-член класса для каждого прослушивателя, и мне просто интересно, какой шаблон проектирования будет более подходящим для этого. Мне нужны только объекты-члены, чтобы я мог delete использовать их в конце. Я думал, что мог бы просто использовать массив для их хранения, и это немного упростило бы его, но я также подумал, что может быть лучший способ, когда вы можете просто вызвать статическую функцию, аналогичную DropListener::RegisterListener(CWnd* wnd, CStringamp; extension, void(*callback) callback) , и она обрабатывает все создание / регистрацию / удаление для вас.

Ответ №1:

Я не знаком с MFC, но с точки зрения OO ваш дизайн можно улучшить.

Сначала определите, какие аспекты ваших требований, скорее всего, изменятся, а затем определите, какие интерфейсы необходимы для изоляции этих изменений:

Изменения:

  • То, что вы хотите прослушать (событие)
  • Действие, которое вы хотите предпринять (обратный вызов)

Интерфейсы:

  • Механизм добавления обратного вызова, связанного с событием, в уведомитель о событии
  • Механизм вызова обратного вызова из уведомителя

Итак, вам нужен Event интерфейс, Callback интерфейс и Notifier интерфейс.

В C есть удобная вещь, называемая std::function<T> where T — любой вызываемый тип (указатель на функцию, a functor , a lambda ). Поэтому вам, вероятно, следует использовать это для инкапсуляции ваших обратных вызовов, чтобы предоставить вашему пользователю больше свободы.

Тогда какие типы событий вы хотите поддерживать? Это покажет вам, нужно ли вам поддерживать разные Event объекты, а также как будет выглядеть ваша регистрация:

 // For example if you support just `Drop` events:
void addDropListener(std::function<T> callback);

// If you support many events:
void addListener(Event::Type evType, std::function<T> callback);
  

После того, как вы ответили на этот вопрос, вам нужно решить, как выглядит «обратный вызов» ( T в приведенных выше примерах). Это может возвращать значение (если вам нужна проверка успеха) или вызывать определенный тип исключения (обязательно документируйте контракт). Затем спросите, хотите ли вы получить копию события, которое было запущено (обычно вы это делаете). Предполагая, что вы рады получать уведомления только об ошибках через исключения, тогда вы можете ввести ожидаемый std::function следующим образом:

 typedef std::function<void (const Eventamp;)> EventCallback;
  

Тогда я рекомендую вашему Notifier разработчику использовать std::vector<EventCallback> or std::map<Event::Type, std:vector<EventCallback> . Первый вариант полезен, если вы хотите поддерживать только один тип событий или вызывать всех прослушивателей для всех событий. Второй вариант удобен, когда вы хотите уведомлять прослушивателей только об определенных типах событий.

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

Надеюсь, это помогло. 🙂