views:

230

answers:

3

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.

A: 

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.

jdv
Well, but what and how would you aggregate here in this particular example?
@wwosik: I edited my anser (this morning, actually) in response to your comment. I'm not sure SO is notifying you of this hence this comment.
jdv
+1  A: 

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.

Travis Heseman
Well this inheritance tree won't be long, it's abstract->concrete and that's that :)
@after edit: I must say I actually like your parent/child idea. :)
I had done something similar with a deeper hierarchy. Had several levels of ParentAndChild classes before getting to the leaf children.
Travis Heseman
A: 

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.

Aidan Cully
Well, "I'm not familiar with this" is a rather important argument in case of a development team. See here (shameless ad): http://stackoverflow.com/questions/2320371/legible-or-not-c-multiple-ternary-operators-throw-if-unmatched . Note generics is more efficient than inheritance, not other way round. ;)
I don't think the generics are actually more complicated than inheritance... What the team is used to may increase the _perceived_ complexity, but that's all the more reason to experiment with the more rigorous method, so that the team gets used to more robust techniques.
Aidan Cully