0

I've started working on a C# codebase. There are three services which run the same set of steps of three kinds of objects, each returning IResult:

  • public IResult FooService(Foo foo) { ... }
  • public IResult BarService(Bar bar) { ... }
  • public IResult BazService(Baz baz) { ... }

Foo, Bar, and Baz share common properties, and the services use some of those common properties as well as properties unique to each.

There is no code sharing between these services - each one is basically copy-pasted, with particular steps in each service tweaked where necessary. Some of the steps are identical across all three services.

I want to refactor this to reduce the amount of duplicated code. The template method pattern seems to do exactly what I want. I can share common steps while allowing each service to override them with its own behaviour where needs. Here is how I could define the abstract template service class:

public abstract class BaseService<T> where T : IFooBarBaz
{
    public IResult ProcessInput(T input)
    {
        Step1();
        Step2();
        return Step3();
    }

    protected virtual void Step1()
    {
        // default implementation
    }

    protected abstract void Step2();

    private IResult Step3()
    {
        // common implementation
    }
}

and an implementation:

public class FooService : BaseService<Foo>
{
    protected override void Step1()
    {
        // Foo-specific implementation
    }

    protected override void Step2()
    {
        // Foo-specific implementation
    }
}

Another implementation which uses the base class implementation for Step1:

public class BarService : BaseService<Bar>
{
    protected override void Step2()
    {
        // Bar-specific implementation
    }
}

I like this pattern, but having some of the functionality 'hidden' in BaseService might make it difficult to follow. Another way might be to extract the common steps to a helper class, and let each service choose for each step whether to call the helper class method or write its own. This avoids the use of inheritance.

Is the template pattern shown above a good choice here, or is there a better way to do this?

1
  • 2
    I prefer simple code with some duplication over clever but hard to reason about abstractions. First thing I’d look into was how I could remove these services by incorporating their behavior into the Foo, Bar and Baz objects. Start with basic encapsulation before pulling out fancy patterns. Not an answer to your question, but give it a thought.
    – Rik D
    Commented Nov 5, 2023 at 15:25

1 Answer 1

0

Inheritance is good for polymorphism. Other classes interact with the base class definition, but the object they deal with can be an inherited type.

Inheritance is not an ideal way to share code between classes. It couples them together. We're likely to find ourselves having to make changes to the base class just because of the particular needs of one inherited class. But changing the base class potentially impacts all of the inherited classes.

Or the base class ends up with lots of extra code because some inherited classes need these methods and some need those methods. If we're using dependency injection we'll have to inject all the things that all the methods need. So the inherited classes end up with dependencies in their constructors that they don't even need.

In this particular example, consider a few examples of where things can go haywire:

  • All of your inherited classes start off with three steps, but then one of them needs four steps.
  • Several inherited classes share code for a few steps, so perhaps you create another base class that inherits from the base but provides some of that shared code. But then one of them changes. Maybe it ends up sharing the same Step 1 but now Step 2 is more similar to another existing class. Trying to use inheritance to manage all of this gets complicated.

In some cases you can work around it with virtual methods and inherited methods that call base methods, but why? It's still complicated and confusing.


After all that, what is a better way to share code? Composition. There's a principle - "Prefer composition over inheritance." I won't link to anything in case it gets broken, but you can Google.

Whatever the code is that you want to share, put it in a class of its own or a static method. Then each service is a composition of the things it needs to do its job.

At that point each service class is separate from the others, not linked together by a common base class. Each has what it needs to do what it does. They're free to grow and change independently.

Another benefit is that if needed you can more easily unit test those smaller classes or static methods. That can be more confusing if you're trying to test them through inherited classes.

When all of your classes are free from coupling to each other you might see small amounts of things that look like duplication. Maybe some will depend on a few of the same steps. That's probably coincidental. I wouldn't be aggressive about "fixing" that duplication. Only address it if it involves duplications of either business logic or large amounts of code. If two classes do similar but different things then it's okay for them to include similar code.

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.