Виртуальные методы или события в C#

Я в настоящее время пишу немного библиотеки C# для упрощения реализации небольших физических моделирований/экспериментов.

Основной компонент является a SimulationForm это выполняет цикл таймера внутренне и скрывает шаблонный код от пользователя. Сам эксперимент будет просто определен через три метода:

  1. Init() (Инициализируйте все),
  2. Render(Graphics g) (Представьте текущее состояние моделирования),
  3. Move(double dt) (Переместите эксперимент для dt секунды)

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

1) Виртуальные методы, которые будут переопределены наследовавшейся формой

protected virtual void Init() {} 
...

или

2) События

public event EventHandler<MoveSimulationEventArgs> Move = ...
...

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

Прохладная вещь об этом являющемся "нормальной формой" состоит в том, что можно записать

partial class frmMyExperiment : SimulationForm {
}

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

9
задан Dario 16 December 2009 в 20:40
поделиться

7 ответов

В этом случае я предпочитаю использовать виртуальные методы.

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

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

17
ответ дан 4 December 2019 в 06:11
поделиться

Небольшая подсказка для принятия решения:

Вы хотите уведомить другие (несколько) объектов? Затем используйте события.

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

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

5
ответ дан 4 December 2019 в 06:11
поделиться

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

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

Вот некоторые из моих общих наблюдений о выборе между этими шаблонами:

События прекрасны:

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

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

Виртуальные методы работают хорошо:

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

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

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

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

15
ответ дан 4 December 2019 в 06:11
поделиться

Есть еще один вариант, заимствованный из функционального программирования: выставить точки расширения как свойства типа Func или Action, и пусть ваши эксперименты предоставят делегаты таким образом.

Например, в вашей программе моделирования, у вас будет:

public class SimulationForm
{
    public Action Init { get; set; }
    public Action<Graphics> Render { get; set; }
    public Action<double> Move { get; set; }

    //...
}

И в вашей симуляции у вас будет:

public class Simulation1
{
    private SimulationForm form;

    public void Simulation1()
    {
        form = new SimulationForm();
        form.Init = Init;
        form.Render = Render;
        form.Move = Move;
    }

    private void Init()
    {
        // Do Init code here
    }

    private void Render(Graphics g)
    {
        // Do Rendering code here
    }

    private void Move(double dt)
    {
        // Do Move code here
    }
}

Edit : Очень глупый пример именно этой техники, которую я использовал в некотором закрытом коде (для частной игры project), ознакомьтесь с моей записью в блоге о коллекции, созданной делегатами .

3
ответ дан 4 December 2019 в 06:11
поделиться

ни то, ни другое.

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

2
ответ дан 4 December 2019 в 06:11
поделиться

Почему интерфейс не подходит для этого?

1
ответ дан 4 December 2019 в 06:11
поделиться

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

По сути, вы можете использовать события для представления «вывода» класса, помимо методов возврата .

0
ответ дан 4 December 2019 в 06:11
поделиться
Другие вопросы по тегам:

Похожие вопросы: