Какой лучший способ повторно использовать реализацию: наследование или генерики?
Модель следующая: в скрипте есть шаги, в шагах есть элементы. Древовидная структура имеет двойную связь, то есть ступени теперь знают свои сценарии, а элементы теперь свои ступени.
Теперь есть 2 типа сценариев: шаблоны и прогоны, где прогон сначала создается как копия шаблона. Это приводит к 2 подобным иерархиям ScriptTemplate-> ScriptTemplateStep-> ScriptTemplateElement и ScriptRun-> ScriptRunStep-> ScriptRunElement. Большая часть функциональности является общей, но различные классы могут иметь некоторые дополнительные свойства.
Для повторного использования функциональности я мог бы разработать абстрактный класс Script, который будет производным от ScriptRun и ScriptTemplate, например:
abstract class Script { IList<Step> Steps; }
class ScriptRun : Script {}
class ScriptTemplate : Script {}
class Step { Script Script; IList<Element> Elements; }
class ScriptRunStep : Step {}
class ScriptTemplateStep : Step {}
или я мог бы попробовать дженерики:
abstract class Script<TScript, TStep, TElement>
where TScript:Script<TScript, TStep, TElement>
where TStep:Step<TScript, TStep, TElement>
where TElement:Element<TScript, TStep, TElement>
{ IList<TStep> Steps; }
abstract class Step<TScript, TStep, TElement>
where TScript:Script<TScript, TStep, TElement>
where TStep:Step<TScript, TStep, TElement>
where TElement:Element<TScript, TStep, TElement>
{ TScript Script; IList<TElement> Elements; }
class ScriptRun : Script<ScriptRun, ScriptRunStep, ScriptRunElement> {}
class ScriptRunStep : Step<ScriptRun, ScriptRunStep, ScriptRunElement> {}
class ScriptRunElement : Element<ScriptRun, ScriptRunStep, ScriptRunElement> {}
class ScriptTemplate : Script<ScriptTemplate, ScriptTemplateStep, ScriptTemplateElement> {}
class ScriptTemplateStep : Step<ScriptTemplate, ScriptTemplateStep, ScriptTemplateElement> {}
class ScriptTemplateElement : Element<ScriptTemplate, ScriptTemplateStep, ScriptTemplateElement> {}
Минусы универсального подхода:
- Поначалу кажется немного сложным. Особенно, где ужасны.
- Поначалу кажется не знакомым.
- Приносит немного веселья, когда DataContractSerializing его.
- Сборка больше.
Плюсы:
- Тип защиты: вы не сможете добавить ScriptTemplateElement в ScriptRunStep.
- Не требует приведения к конкретному типу из коллекционных предметов. Также - лучшая поддержка intellisense. ScriptTemplate.Steps - это немедленно ScriptTemplateStep, а не абстрактный шаг.
- В соответствии с принципом Лискова: в сценарии наследования у вас есть коллекция IList на ScriptRun, но вы действительно не должны добавлять к ней ScriptTemplateStep, хотя это явно Step.
- Вам не нужно делать переопределения. Например. Предположим, вы хотите иметь метод NewStep в скрипте. В первом сценарии вы говорите
abstract class Script { abstract Step NewStep(); }
abstract class ScriptRun {
override Step NewStep(){
var step = new ScriptRunStep();
this.Steps.Add(step);
return step;
}
}
abstract class ScriptTemplate {
override Step NewStep(){
var step = new ScriptTemplateStep();
this.Steps.Add(step);
return step;
}
}
В общем сценарии вы пишете:
abstract class Script<TScript, TStep, TElement>
where TScript:Script<TScript, TStep, TElement>
where TStep:Step<TScript, TStep, TElement>, new()
where TElement:Element<TScript, TStep, TElement>
{
TStep NewStep() {
var step = new TStep();
this.Steps.Add(step);
return step;
}
}
и ScriptRun и ScriptTemplate автоматически имеют этот метод или, что еще лучше, с возвращаемым типом соответственно ScriptRunStep и ScriptTemplateStep вместо простого Step.