开发者

Code reuse through generics vs polymorphism

What is the better way to reuse implementation: inheritance or generics?

The model is following: Script has Steps, Steps have Elements. Tree structure is double linked, i.e. Steps know their Script and Elements now their Step.

Now, there are 2 types of Scripts: Templates and Runs, where a Run is created at first as a copy of the Template. This results in 2 similar hierarchies ScriptTemplate->ScriptTemplateStep->ScriptTemplateElement and ScriptRun->ScriptRunStep->ScriptRunElement. Most of the functionality is common, but various classes may have some additional properties.

To reuse functionality I could develop abstract Script class which would be derived by ScriptRun and ScriptTemplate like:

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 {}

or I could try generics:

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> {}

The cons of generics approach:

  1. Seems a bit complicated at first. Especially wheres are awful.
  2. Seems not familiar at first.
  3. Brings a bit of fun when DataContractSerializing it.
  4. Assembly is larger.

The pros:

开发者_高级运维
  1. Type security: you won't be able to add a ScriptTemplateElement to a ScriptRunStep.
  2. Doesn't require casting to a concrete type from collection items. Also - better intellisense support. ScriptTemplate.Steps are instantly of ScriptTemplateStep, not abstract Step.
  3. Abides by Liskov principle: in inheritance scenario, you have IList collection on ScriptRun, but you really shouldn't add ScriptTemplateStep to it, altough it is clearly a Step.
  4. You don't have to do overrides. E.g. suppose you want to have a NewStep method on the Script. In the former scenario you say

:

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; 
    } 
}

In the generics scenario you write:

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;
    }
}

and ScriptRun and ScriptTemplate automatically have that method, or an even better one: with a return type of respectively ScriptRunStep and ScriptTemplateStep instead of simply a Step.


I find that generics facilitate composition through generic properties without having to write different classes for each composition you want to leverage or having to create a lengthy inheritance tree. I try to favor composition to inheritance when I can, especially in a single-inheritance platform.

I'd say your situation warrants a little bit of both. Perhaps something like the following:

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> { ... }

Something like this could facilitate much of the functionality in a hierarchy of double-linked objects.


Neither. The best way to reuse implementation is aggregation.

After that, it depends on the problem if templates are appliccable. My rule of thumb is that whatever produces the least amount of code is then better. At least, that's my experience.

[Edit] That was perhaps a little short answer for such an elaborate question.

But, specifically for this case (which appears to be mostly about reusing the logic of the parent-child hierarchy, you could write

 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 will have one Children<> collection as a member, and passes it a pointer to itself. The collection ensures that each TCHILD has a reference to its parent.


Look at your list of pros and cons - I suspect you already know the answer. The cons are generally of the form "I'm not as familiar with this", "it's not as efficient", while the pros are of the form "it enforces a more rigorous structure on my code". I say, always go for the one that makes the code more robust, and that's generics.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜