Повторное использование кода через дженерики против полиморфизма - PullRequest
0 голосов
/ 24 февраля 2010

Какой лучший способ повторно использовать реализацию: наследование или генерики?

Модель следующая: в скрипте есть шаги, в шагах есть элементы. Древовидная структура имеет двойную связь, то есть ступени теперь знают свои сценарии, а элементы теперь свои ступени.

Теперь есть 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> {}

Минусы универсального подхода:

  1. Поначалу кажется немного сложным. Особенно, где ужасны.
  2. Поначалу кажется не знакомым.
  3. Приносит немного веселья, когда DataContractSerializing его.
  4. Сборка больше.

Плюсы:

  1. Тип защиты: вы не сможете добавить ScriptTemplateElement в ScriptRunStep.
  2. Не требует приведения к конкретному типу из коллекционных предметов. Также - лучшая поддержка intellisense. ScriptTemplate.Steps - это немедленно ScriptTemplateStep, а не абстрактный шаг.
  3. В соответствии с принципом Лискова: в сценарии наследования у вас есть коллекция IList на ScriptRun, но вы действительно не должны добавлять к ней ScriptTemplateStep, хотя это явно Step.
  4. Вам не нужно делать переопределения. Например. Предположим, вы хотите иметь метод 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.

Ответы [ 3 ]

2 голосов
/ 24 февраля 2010

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

Я бы сказал, что ваша ситуация требует немного и того, и другого. Возможно что-то вроде следующего:

class Child<TParent> { TParent Parent; }
class Parent<TChild> { IList<TChild> Children; }
class ParentAndChild<TParent, TChild> : Parent<TChild> { TParent Parent; }

class Element : Child<Step> { ... }
class Step : ParentAndChild<Script, Element> { ... }
class Script : Parent<Step> { ... }

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

1 голос
/ 24 февраля 2010

Ни. Лучший способ повторно использовать реализацию - агрегация.

После этого все зависит от проблемы, применимы ли шаблоны. Мое эмпирическое правило заключается в том, что чем меньше кода создается, тем лучше. По крайней мере, это мой опыт.

[Изменить] Возможно, это был небольшой ответ на такой сложный вопрос.

Но, специально для этого случая (который, как представляется, главным образом касается повторного использования логики иерархии родитель-потомок, вы могли бы написать

 IChildOf<TPARENT> 
 { TPARENT Parent {get; set;} }


 sealed class Children<TPARENT,TCHILD> : where TCHILD: IChildOf<TPARENT>
 {
      // .... details omitted 
      public void Add(TCHILD child)
      { 
          // add child to internal collection, then:

          child.Parent = parent;
      }

      readonly TPARENT parent;
 }

TPARENT будет иметь одну коллекцию Children <> в качестве члена и передает ей указатель на себя. Коллекция гарантирует, что каждый TCHILD имеет ссылку на своего родителя.

0 голосов
/ 24 февраля 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...