Я в настоящее время пишу немного библиотеки C# для упрощения реализации небольших физических моделирований/экспериментов.
Основной компонент является a SimulationForm
это выполняет цикл таймера внутренне и скрывает шаблонный код от пользователя. Сам эксперимент будет просто определен через три метода:
Init()
(Инициализируйте все),Render(Graphics g)
(Представьте текущее состояние моделирования),Move(double dt)
(Переместите эксперимент для dt
секунды)Я просто задался вопросом, что является лучшим выбором для разрешения пользователю реализовать эти функции:
1) Виртуальные методы, которые будут переопределены наследовавшейся формой
protected virtual void Init() {}
...
или
2) События
public event EventHandler<MoveSimulationEventArgs> Move = ...
...
Править: Обратите внимание, что методы не должны быть абстрактными так или иначе. На самом деле, существуют еще больше, и ни один из них не должен быть реализован. Часто удобно пропустить их, так как многим моделированиям не нужны они.
Прохладная вещь об этом являющемся "нормальной формой" состоит в том, что можно записать
partial class frmMyExperiment : SimulationForm {
}
и Вы отлично в состоянии взаимодействовать с разработчиком и всеми наследованными средствами управления и настройками/свойствами. Я не хочу проигрывать, это показывает при наличии совершенно другого подхода.
В этом случае я предпочитаю использовать виртуальные методы.
Кроме того, если методы требуются для работы, вы можете сделать свой класс абстрактным класс . Это лучший вариант с точки зрения удобства использования, поскольку заставляет компилятор принудительно применять его. Если пользователь попытается использовать ваш класс / форму без реализации методов, компилятор будет жаловаться.
События будут здесь немного неуместными, поскольку на самом деле это не то, что вам нужно больше, чем одна реализация (события допускают несколько подписчиков), и концептуально это в большей степени функциональность объекта, а не уведомление, вызванное объектом.
Небольшая подсказка для принятия решения:
Вы хотите уведомить другие (несколько) объектов? Затем используйте события.
Вы хотите сделать возможными различные реализации / использовать полиморфизм? Затем используйте виртуальные / абстрактные методы.
В некоторых случаях даже сочетание обоих способов является хорошим решением.
Интересно, что мне пришлось сделать аналогичный выбор в дизайне, над которым я недавно работал. Один подход не может быть лучше другого.
В вашем примере, без дополнительной информации, я, вероятно, выбрал бы виртуальные методы - тем более, что моя интуиция подсказывает мне, что вы будете использовать наследование для моделирования различных типов экспериментов, и вам не нужна возможность иметь несколько подписчиков (насколько позволяют события).
Вот некоторые из моих общих наблюдений о выборе между этими шаблонами:
События прекрасны:
Самая большая проблема с событиями заключается в том, что управление временем жизни подписчиков может стать сложной задачей, и вы можете внести утечки и даже функциональные дефекты, если подписчики остаются в подписке дольше, чем необходимо. Вторая по величине проблема заключается в том, что разрешение нескольких подписчиков может создать запутанную реализацию, в которой отдельные подписчики наступают друг на друга или демонстрируют зависимости порядка.
Виртуальные методы работают хорошо:
Самая большая проблема с виртуальными методами заключается в том, что вы можете легко ввести проблема хрупкого базового класса в вашу реализацию. Виртуальные методы - это, по сути, контракт с производными классами, который вы должны четко задокументировать, чтобы наследники могли предоставить значимую реализацию.
Вторая самая большая проблема с виртуальными методами заключается в том, что они могут ввести глубокое или широкое дерево наследования только для настройки того, как поведение класса настраивается в конкретном случае. Это может быть нормально, но я обычно стараюсь избегать наследования, если в проблемной области нет четкого отношения is-a
.
Есть другое решение, которое вы могли бы использовать: шаблон стратегии . Пусть ваш класс поддерживает назначение объекта (или делегата), чтобы определить, как должны вести себя Render, Init, Move и т. Д. Ваш базовый класс может предоставлять реализации по умолчанию, но позволяет внешним потребителям изменять поведение. Хотя это похоже на событие, преимущество состоит в том, что вы можете возвращать значение в вызывающий код, и вы можете использовать только одного подписчика.
Есть еще один вариант, заимствованный из функционального программирования: выставить точки расширения как свойства типа 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), ознакомьтесь с моей записью в блоге о коллекции, созданной делегатами .
ни то, ни другое.
вам следует использовать класс, реализующий интерфейс, определяющий требуемые функции.
Используйте виртуальный метод, чтобы разрешить специализацию базового класса - внутренние детали.
По сути, вы можете использовать события для представления «вывода» класса, помимо методов возврата .