Виртуальные обработчики событий из нескольких классов: множественное наследование или композиция?

Моя команда написала несколько классов C ++, которые реализуют обработку событий с помощью чисто виртуальных обратных вызовов — например, когда сообщение получено от другого процесса, базовый класс, который обрабатывает обмен сообщениями IPC, вызывает свою собственную чисто виртуальную функцию, а производный класс обрабатывает событие в переопределение этой функции. Базовый класс знает, что событие произошло; производный класс знает, что с ним делать.

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

  • (1) состав: производные классы от каждого из базовых классов обработки событий и добавление объектов этих производных классов в мой новый класс в качестве членов, или:

  • (2) множественное наследование: сделать мой новый класс производным от всех базовых классов для обработки событий.

Я пробовал оба (1) и (2), и я не удовлетворен моей реализацией либо.

Есть дополнительная сложность: некоторые базовые классы были написаны с использованием методов инициализации и завершения вместо использования конструкторов и деструкторов, и, конечно, эти методы имеют одинаковые имена в каждом классе. Таким образом, множественное наследование вызывает неоднозначность имени функции. Разрешимый с using декларации и / или явная область видимости, но не самая удобная на вид вещь, которую я когда-либо видел.

Даже без этой проблемы использование множественного наследования и переопределение каждой чистой виртуальной функции из каждого из нескольких базовых классов сделает мой новый класс очень большим, граничащим с «объектами Бога». Поскольку требования изменяются (читай: «по мере добавления требований»), это не будет хорошо масштабироваться.

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

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

Итак, множественное наследование, композиция или, может быть, что-то еще целиком? Существует ли общее практическое правило о том, как лучше всего подойти к такого рода вещам?

2

Решение

Из вашего описания я думаю, что вы выбрали подход стиля «шаблонный метод», при котором база работает, а затем вызывает чистый виртуальный объект, который реализует производный класс, а не подход «интерфейс обратного вызова», который почти такой же, за исключением того, что Чистый виртуальный метод находится на совершенно отдельном интерфейсе, который передается в «базу» в качестве параметра для конструктора. Я лично предпочитаю позднее, так как считаю, что это значительно более гибко, когда приходит время соединять объекты вместе и создавать объекты более высокого уровня.

Я склонен идти на композицию с классом создания, реализующим интерфейсы обратного вызова, которые требуются для составных объектов, и затем потенциально снова создавать в подобном стиле на более высоком уровне.

Затем вы можете решить, уместно ли сочинять, если составляющий объект реализует интерфейсы обратного вызова и передает их «составленным» объектам в их конструкторах, ИЛИ вы можете реализовать интерфейс обратного вызова в своем собственном объекте, возможно, с более простым и точным обратным вызовом. интерфейс, который реализует ваш создающий объект, и создает «базовый объект» и «объект реализации обратного вызова» …

Лично я бы не стал использовать интерфейс «абстрактной обработки событий», так как предпочел бы, чтобы мой код был явным и понятным, даже если это приводит к тому, что он немного менее универсален.

1

Другие решения

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

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

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

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

0