Что лучше: переопределение функции или передача указателя на функцию для обработки событий - PullRequest
4 голосов
/ 10 ноября 2010

Итак, я пишу код для класса, который войдет в библиотеку, которая будет использоваться другими.Этот класс будет перехватывать и обрабатывать входящие сообщения (детали не важны, но он использует библиотеку activemq-cpp).Схема этого потребительского класса:

class MessageConsumer {
    ...
    public:
        void runConsumer();
        virtual void onMessage(const Message* message);
}

, где runConsumer() устанавливает соединение и начинает прослушивание, и onMessage() вызывается при получении сообщения.

У меня такие вопросы:Люди, которые будут использовать этот код, будут по-своему обрабатывать разные сообщения.Как я могу оставить MessageConsumer универсальным, но предложить такую ​​гибкость, сохраняя их код простым?

Два варианта:

  • Должны ли они наследовать новый класс от MessageConsumer и написатьсобственные onMessage()?
  • Должны ли они передавать указатель на функцию обработки сообщений на MessageConsumer?

Как вы думаете, какой вариант лучше и почему?

Спасибо!

Ответы [ 4 ]

6 голосов
/ 10 ноября 2010

В одном подходе клиентам разрешается регистрировать обратный вызов, а затем MessageConsumer вызывает зарегистрированный обратный вызов. Это что-то вроде схемы проектирования наблюдателя / трансляции.

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

Основные цели проектирования предлагают использовать самые слабые отношения, чтобы способствовать слабой связи. Поскольку наследование является более сильным отношением по сравнению с простой ассоциацией, все остальное, являющееся тем же подходом 1, является предпочтительным.

С Статья Херба

"Наследование часто злоупотребляется, даже опытными разработчиками. Всегда свести к минимуму сцепление: если класс отношения могут быть выражены в более чем один способ, используйте самый слабый отношения это практично. Дано что наследство почти самые крепкие отношения, которые вы можете выразить в C ++ (второй после дружбы), это только уместно, когда нет эквивалента слабее альтернатива. "

Но, как отмечает Джеймс, сложно комментировать, если общие конструктивные ограничения не известны четко.

1 голос
/ 10 ноября 2010

Чистые виртуальные функции позволяют компилятору проверять, что клиентский код реализует обработчик.Виртуальная диспетчеризация активна сразу после создания объекта, и тот, кто смотрит на производный класс, может точно рассуждать о его обработке.Данные, необходимые для обработки, могут быть удобно и четко сгруппированы в производный класс.Фабрики по-прежнему могут выбирать конкретный производный класс для создания экземпляра.

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

Третий вариант - это класс политики шаблонов или шаблон любопытного повторения шаблона, чтобы заблокировать поведение во время компиляции.Это потенциально позволяет встраивать обратные вызовы, устранение мертвого кода и другие оптимизации.

1 голос
/ 10 ноября 2010

Наследование сделает вашу библиотеку более понятной и может улучшить читаемость. Но на самом деле, варианты примерно одинаковы, так как компилятор проверит, что пользователь предоставил функцию (при условии, что вы объявите чистый виртуальный обработчик в базовом классе), и базовый механизм будет выполнен через указатель в любом случае (виртуальная таблица дело о наследовании).

0 голосов
/ 10 ноября 2010
Виртуальная функция

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

Чтобы проиллюстрировать это, подход с указателем на функцию можно заключить в первые два, но не наоборот.

void cbFunction();

class Interface {
  virtual void act() =0 ;
};

class CbFuctionWrapper:public Interface {
public:
  virtual void act() {cbFunction()};
};

class AnotherImpl: public Interface {
  Context _c; // You can't pass additional context with usual function without downcasting, but OO is all about that.
public:
  virtual void act() {...}
}
...