2

At the time I'm workin on a project with several Instances of an abstract class. Some functions in the subclasses are the same, some not. This would lead to code like the following. In the Code i use object as parameter, but in the real project it's a defined class and not object.

public abstract class BaseClass
{
    protected BaseClass Next;
    public abstract void foo(object SomeParameters);
    protected virtual void bar1()
    {
        object SomeParameters = this.GetMyParameters();
        foo();
        bar2();
    }

    public virtual void bar2()
    {
        if (this.Next!= null)
        {
            this.Next.bar2();
        }
    }
}

//==============================
//==============================

public class Child1 : BaseClass
{
    override public void foo(object SomeParameters)
    {
         ...
    }
}

public class Child2 : BaseClass
{
    override public void foo(object SomeParameters)
    {
         //some code
    }

    override public void bar2()
    {
         this.DoSomething();
         base.bar2();
    }

    private void DoSomething()
    {
        //some other code
    }
}

public class Child3 : BaseClass
{
    override public void foo(object SomeParameters)
    {
         ...
    }

    override public void bar1()
    {
         object SomeParameters = this.GetMyParameters();
         foo();
         Between();
         bar2();
    }

    private void Between();
    {
        //Code between foo and bar2
    }
}

My doubts are the readability of the code, because we're talking about something like 15 subclasses. The other Way around would be to code things twice or more times, a violation of the DRY principle. Is there any solution/Pattern i missed to get rid of this paradox?

Greetings Noran

  • There are a number of quite different ways of working around inheritance here, but which fits best depends on how these subtypes are used. Can you give a more concrete example of the difference between various bar implementations and how the types are used the system? Until then, answers will be largely guesswork. – David Arno Oct 21 '16 at 11:48
  • I don't see what the problem is that you're trying to solve. The code you have already looks good to me. – Tanner Swett Oct 21 '16 at 11:52
  • @TannerSwett: the problem is the readability of the code. if I override in some Subclasses the functions and in some not, it's easy to lose the trace. – Noran Oct 21 '16 at 12:14
  • @DavidArno I added some additional Code – Noran Oct 21 '16 at 12:16
1

Template method design pattern

Your design defines in the base class, a template method design pattern, bar1() being the template method and foo() and bar2() are the primitives called by that method.

In Child2 you seem to extend the logic of the template method bar1() by inserting an extra primitive Between() in the logic. Unfortunately, this primitive is class specific, so you need to override bar1() for that purpose in that class.

To improve greatly the readability of your code and simplify the maintenance, I'd suggest to refactor that code so to have only one common (full) version of the template method. So you need only to understand the general logic of the template method, and then understand what's specific in each class.

 public abstract class BaseClass
 {
    ...
    protected virtual Between() {}  // by default, nothing
    public void bar1()              // one single skeleton
    {
         foo();
         Between();
         bar2();
    }
    ...
}
... 
public class Child3 : BaseClass
{
    override protected void Between();
    {
        //Code between foo and bar2
    }
}

By the way, looking closely, bar2() is also a kind of template method

public abstract class BaseClass
{
    ...
    protected virtual void DoSomethingBefore() {} 
    protected virtual void DoSomethingAfter() {} 
    public virtual void bar2()
    {
        DoSomethingBefore(); 
        if (this.Next!= null)
        {
            this.Next.bar2();
        }
        DoSomethingAfter(); 
    }
}
...
public class Child2 : BaseClass
{
    ...
    override protected void DoSomethingBefore()
    {
        ...
    }
}

Some private functions are promoted to protected ones. But it's utility functions still not accessible for the outside world. The drawback is that you have a slight overhead, calling sometimes empty primitives.

This pattern makes sense only if there are slight variations in the methods, but the core is the same for all.

Strategy design pattern

Another way to handle it is to use the strategy design pattern. The idea is to identify generic behaviours/algorithms and factor them out of your class hierarchy, to move them to interchangeable strategy classes.

You could implement static strategies using interfaces. Or you could implement dynamic strategies using composition.

The advantage is that it is far more flexible than the template method. However it is very different in structure: the strategy's object methods need to have access to the Childclasses. Thus they are limited to public access, which may force you to make some helper functions public instead of keeping them private.

  • After some investigation of the customer system specification i decided to use a combination of both. I'll take a template which can be called from derived classes. Because before using the template, i have get the parameters for it out of some TCP/IP-telegram. Thank you for the approach. – Noran Oct 24 '16 at 8:20
2

You're thinking of the Strategy Behavior Pattern

You seem to be interested in using several classes with slightly different functionality. You did mention in your comment to Kilian Foth that these are hardware with different jobs. I know that every Hardware has some set of Jobs. I also know (since you didn't say but usually this is what a Job is and I'm making an educated guess) that the Jobs have several steps that need to be completed in order.

public abstract class Hardware 
{
    private List<Job> CurrentJobs;
    //Some set of other things that I don't care about.

    public void AddJob(Job j){
        CurrentJobs.Add(j);
    }

    public virtual void ExecuteJobs(){
        foreach (Job j in CurrentJobs)
        {
            j.Execute(argument);
        }
    }

    //Other non-virtual methods to manipulate CurrentJobs
    //Other methods as needed
}

public abstract class Job
{
    public virtual void Execute(Argument argument)
    {
        //Default behavior
    }
}

public class FancyJob : Job
{
    public virtual void Execute(Argument argument)
    {
        //Specialized Behavior
    }
}

This is kind of a very basic example to get the point across. Basically, if you have a set kind of job that only operates on the same thing (maybe?) you can tell it a list, load that list in order, and then execute the jobs without knowing what they are. Effectively, you want to make the hardware stupid enough that all it knows about are the names of the jobs it needs to do.. This way too if you have a particular job/step that's similar, you can re-use the job in multiple places.

If you want to get very fine grained, each Job could have as well a list of steps to execute through the same mechanism.

1

Use a functional approach.

public class BaseClass {
    public BaseClass(Action foo, Action bar1 = null, Action bar2 = null) {
        this.foo = foo;
        this.bar1 = bar1 ?? this.defaultBar1;
        this.bar2 = bar2 ?? this.defaultBar2;
    }

    private void defaultBar1() 
    {
    }

    private void defaultBar2() 
    {
    }

    private Action foo;
    private Action bar1;
    private Action bar2;
}

You can create as many impls as you want with relative ease. This interface can be tweaked to permit accessing BaseClass instance, allowing your own variables, etc.

  • 2
    This would only relocate the problem from overrides to Actioncalls. The readability don't get improved. – Noran Oct 21 '16 at 11:59
0

The point of inheritance is precisely this: to allow subclasses to override a few methods while reusing all others. If your code is structured this way and becomes unreadable, it's not because of the overriding, but because there are so many subclasses.

Do you need 15 subclasses? What do they represent? Can you refactor that so that there are fewer classes and more methods, or more parameters? Would Decorators solve your problem, perhaps?

  • 1
    The Classes represent different Hardware with different Jobs. But some parts of the jobs are the same in different classes and in other classes some jobs differ. But not all. – Noran Oct 21 '16 at 12:01
  • Then perhaps you should have classes which represent different Job Actions not different hardware configurations. – Pete Kirkham Oct 24 '16 at 8:38
-1

You could do it by using more inhereitance or abstract classes.

In your example I think you are saying child1 and child2 might have the same implementation of foo(), however, child2 has a different bar2(). You could therefore make child2 inherit from child1 and therefore you would not have the code for foo() repeated. You could do this for each different implementation of foo()

You can also have multiple abstract classes following this pattern

public abstract class BaseClass
{
    public abstract void foo();
    public abstract void bar();
}

public abstract class BaseClassFoo1 : BaseClass
{
    override public void foo()
    {
        doSomething();
    }
    public abstract void bar();
}

public abstract class BaseClassFoo2 : BaseClass
{
    override public void foo()
    {
        doSomethingElse();
    }
    public abstract void bar();
}

public abstract class BaseClassBar1 : BaseClass
{
    public abstract void foo();
    override public void bar()
    {
        doSomething();
    }
}

public abstract class BaseClassBar2 : BaseClass
{
    public abstract void foo();
    override public void bar()
    {
        doSomethingElse();
    }
}

public class Child1 : BaseClassFoo1
{
    override public void bar()
    {
         doSomething();
    }
}

Whenever you would have repeated code, just make a base class for those classes. It might not be worth it if you would have only two classes with the same code but any more than that and you do not want the repeated code.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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