Интересно, мне пришлось сделать похожий выбор в дизайне, над которым я недавно работал. Один подход не строго превосходит другой.
В вашем примере, без дополнительной информации, я бы, вероятно, выбрал виртуальные методы, особенно потому, что моя интуиция заставляет меня думать, что вы будете использовать наследование для моделирования различных типов экспериментов, и вам не нужно иметь возможность иметь несколько подписчиков. (как позволяют события).
Вот некоторые из моих общих замечаний о выборе между этими образцами:
События замечательные:
- когда вам не нужно, чтобы вызывающий абонент возвращал какую-либо информацию, и когда вам нужна расширяемость, не требуя подклассов.
- , если вы хотите, чтобы у вызывающего абонента было несколько подписчиков, которые могут прослушивать и отвечать на событие.
- потому что они естественным образом располагаются в шаблонном шаблоне , что помогает избежать появления проблемы хрупкого базового класса .
Самая большая проблема с событиями заключается в том, что управление временем жизни подписчиков может оказаться сложным, и вы можете внести утечки и даже функциональные дефекты, когда подписчики остаются подписанными дольше, чем необходимо. Вторая самая большая проблема заключается в том, что разрешение нескольким подписчикам может создать запутанную реализацию, в которой отдельные подписчики наступают друг на друга или демонстрируют зависимости порядка.
Виртуальные методы работают хорошо:
- когда вы хотите, чтобы наследники могли изменять поведение класса.
- когда вам нужно вернуть информацию из звонка (какое событие не поддерживается легко)
- , когда вы хотите, чтобы только один подписчик находился в определенной точке расширения
- когда вы хотите, чтобы производные могли переопределять поведение.
Самая большая проблема с виртуальными методами заключается в том, что вы можете легко внедрить проблему хрупкого базового класса в вашу реализацию. Виртуальные методы - это, по сути, контракт с производными классами, который необходимо четко документировать, чтобы наследники могли обеспечить значимую реализацию.
Вторая самая большая проблема с виртуальными методами - это то, что они могут вводить глубокое или широкое дерево наследования просто для того, чтобы настроить, как поведение класса настраивается в конкретном случае. Это может быть хорошо, но я обычно стараюсь избегать наследования, если в проблемной области нет четких is-a
отношений.
Существует еще одно решение, которое вы могли бы рассмотреть, используя: шаблон стратегии . Попросите вашего класса поддержать назначение объекта (или делегата), чтобы определить, как Render, Init, Move и т.д. должен вести себя Ваш базовый класс может предоставлять реализации по умолчанию, но позволяет внешним потребителям изменять поведение. Хотя аналогично событию, преимущество заключается в том, что вы можете возвращать значение вызывающему коду и применять только одного подписчика.