3

I'm having trouble designing an inheritance hierarchy.

In the figure:

Base class:

  • This uses the Template Method.
  • Contains core logic/algorithm that calls virtual/abstract methods

Derived types:

  • These classes provides concrete implementation of the virtual/abstract methods expected by the Base.

Conditions:

  1. I have a virtual method (Run).
  2. The default Run method uses another virtual method (Proc).
  3. If a derived class uses the default Run behaviour, they must implement Proc.
  4. If a derived class overrides the Run behaviour, then it shouldn't have to care about the existence of Proc, let alone overriding it.

enter image description here

I've considered making Run() abstract and let each derived class handle their own. But then I'll end up with duplicate code.

enter image description here

I'm using a simplified example here.

In my actual project, I have several "Run"-typed virtual methods, each calling several "Proc"-typed methods.

Because of that, I have a whole bunch of virtual "Proc" methods with "throw new NotImplementedException()"

Creating a new derived type is also confusing, because the coder may not know which virtual methods must be implemented/can be ignored/etc.

I've considered using a strategy pattern, but since I have several "Run" methods, each one of those would have to be a strategy, which seems like overkill/overengineering.

enter image description here

Is there any way to simplify this?


[Update 1]

Here's an example that's closer to the kind of methods I'm working with

enter image description here

And imagine yet another method:

enter image description here

9
  • Have you considered creating a default implementation in the top-level class, letting your descendant classes call Proc in the Top-level class, and only overriding Proc if necessary? Commented Jan 8, 2014 at 4:34
  • This sounds like the perfect example for a strategy pattern. You could also have type 2 and type 3 inherit from a type 5 that inherits from Base. This would be the standard inheritance way, but I would stick with strategy. If you later add classes that share run then they can reuse different run methods. Especially if you already have an example of two types sharing a run method.
    – Nick Bray
    Commented Jan 8, 2014 at 5:06
  • "each one of those would have to be a strategy, which seems like overkill/overengineering." I disagree. I think it is perfectly fine to express this behavior in it's own class.
    – Euphoric
    Commented Jan 8, 2014 at 6:47
  • In the update, what corresponds to proc() in the new diagram? getData()?
    – itsbruce
    Commented Jan 8, 2014 at 10:59
  • GetData corresponds to Run, GetSelectionName and GetXmlList to proc. Next example, RunQuery(Run), InsertData(proc).
    – jayars
    Commented Jan 8, 2014 at 11:46

2 Answers 2

2

You have created your own, unnecessary problem here.

  1. Everything must be runnable
  2. The default implementation of run requires proc
  3. Anybody implementing their own run can ignore proc (but all that useless code still adorns the class and any descendants).
  4. Your concrete, exception-throwing implementation of proc enables people to set up themselves up for runtime errors. Why do that?

OK, so you wanted to be helpful by providing a default implementation but you have bolted on implementation details which you know will not always be desired. You say you want to avoid code duplication but you have encumbered your hierarchy with code which will be obsolete as soon as somebody extends it.

I would strongly urge you to go the interface/abstract-base-class route here:

  1. Abstract base class runnable
  2. Abstract base class proccer
  3. Create proc-calling concrete implementation of runnable.
  4. Wherever possible (even in proc-caller class methods) refer to runnables and not proc-callers).

So your default run implentation can be used where desired but can be dropped without penalty. Also, this way people are forced to implement proc if they mix in proc-caller but you don't have to write that "Not implemented" version of proc - they can't compile their code if they do not implement proc. Why create that runtime trap when you can require them to fix things at compile time?

If you think there are other useful behaviours which should be available, you can avoid code duplication by providing "interfaces" in the same way I have shown for proc. Anybody who wants that can mix it in.

1
  • did you mean something like in the 3rd figure (strategy approach)?
    – jayars
    Commented Jan 8, 2014 at 10:52
3

Why not add one more level into your hierarchy?

public class Base
{
    public virtual void Run()
    {
        // some code
    }
}

public class ProcBase : Base
{
    public sealed override void Run() // make sure it cannot be overriden
    {
        Proc();
        base.Run();
    }

    public virtual void Proc()
    { 
        // some code
    }
}

public class Type1 : Base // same with Type4
{
    public override void Run()
    {
        base.Run();
    }
}

public class Type2 : ProcBase // same with Type3
{
    public override void Proc()
    {
        base.Proc();
    }
}

Also, if this hierarchy changes between different Runs, then using strategy pattern becomes inevitable.

3
  • Perhaps Proc() could be made protected?
    – rwong
    Commented Jan 8, 2014 at 8:05
  • @Euphoric the hierarchy does indeed change (I've updated my question). I have several methods like Run(), and it would be hard, if not impossible, to refactor and group multiple base classes
    – jayars
    Commented Jan 8, 2014 at 10:47
  • @jsjslim Then I see no other way than to use Strategy for each different Run.
    – Euphoric
    Commented Jan 8, 2014 at 14:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.