Template Method Pattern. Can we do better?

Often we encounter algorithms with a certain structure that consist of individual steps that may vary for specific implementations.

To keep things clean and in order to reduce duplicate code we should refactor common parts out.

The object-oriented solution to this is the Template Method design pattern.

But there might be an alternative, better solution to this that uses higher-order functions.

Here is an example

These two methods have a common structure that could be generalized to follow DRY:

public static int Sum(IEnumerable<int> seq)
{
    var sum = 0;
    foreach (var i in seq)
        sum += i;
    return sum;
}

public static int Product(IEnumerable<int> seq)
{
    var product = 1;
    foreach (var i in seq)
        product *= i;
    return product;
}

Generalizing the algorithmic structure

An object-oriented solution to this is to apply the Template Method design pattern.

Let’s see how we can refactor the code from above by using the Template Method pattern. Here is an abstract template class that represents the algorithmic structure:

// this implementation is not recommended, it is only for demonstration purposes
public abstract class Aggregator<TSource, TState>
{
    protected TState State;
    protected IEnumerable<TSource> Seq;

    protected Aggregator(IEnumerable<TSource> seq)
    {
        Seq = seq;
    }

    public TState Evaluate()
    {
        SetInitialState();
        foreach (var x in Seq)
            Aggregate(x);
        return State;
    }

    protected abstract void SetInitialState();
    protected abstract void Aggregate(TSource x);
}

Now we can create specific sub-classes that implement Sum and Product:

public class Sum : Aggregator<int, int>
{
    public Sum(IEnumerable<int> seq) : base(seq) { }

    protected override void SetInitialState() { State = 0; }
    protected override void Aggregate(int i) { State += i; }
}

public class Product : Aggregator<int, int>
{
    public Product(IEnumerable<int> seq) : base(seq) { }

    protected override void SetInitialState() { State = 1; }
    protected override void Aggregate(int i) { State *= i; }
}

Here is the usage:

var seq = Enumerable.Range(1, 10);

var sum = new Sum(seq);
var sumResult = sum.Evaluate(); // sumResult = 55

var product = new Product(seq);
var productResult = product.Evaluate(); // productResult = 3628800

Pros

It reduces code duplication.

Cons

There is a ridiculous amount of boiler-plate code involved.

Also, in other more complex scenarios state can be scattered throughout many (sub-) classes and source files that has to be managed and maintained somehow.


(this representation was adopted from Yan Cui from here)

Finally, instantiating objects to be able to call the methods is cumbersome. In general it is just an unwieldy construct.

The light-weight approach

We can achieve the same reduction of code duplication by using higher-order functions. The benefit is that it is much more light-weight.

Higher-order functions ftw

All we have to do is to create a higher-order function that represents the overall structure and that takes the specific parts as arguments. Those specific parts are the same as the abstract methods of the template class that we saw above.

This is the implementation:

public static TState Aggregate<TSource, TState>(this IEnumerable<TSource> seq,
    Func<TSource, TState, TState> aggrgator, TState initial)
{
    var state = initial;
    foreach (var x in seq)
        state = aggrgator(x, state);
    return state;
}

We have to be careful with the parameters’ signatures because the abstract methods of the template class have hidden inputs and outputs. This means that we cannot directly use their signatures for the parameters.

A closer look at the signature

Let’s take a closer look at the signature of Aggregate and compare it to the Template Method implementation.

SetInitialState() from the template class e.g. takes no arguments and returns no values. But it has a hidden output.

The hidden output is the local field State of type TState within the class Aggregator. Therefore the type of the parameter should be Func<TState>.

We will call this parameter initial. Because initial has only an output, but no input and because it is a pure function without side effects, we can replace it with a constant value of type TState.

The abstract method Aggregate(TSource x) from the template class takes one argument and returns no values. But it has hidden inputs and outputs.

The local field State is both input and output. We have to make this explicit by declaring the signature Func<TSource, TState, TState> to the parameter aggregator.

The specific implementations

Here are the specific implementations that make use of our generalized function Aggregate:

public static int Sum(this IEnumerable<int> seq)
{
    return seq.Aggregate((a, b) => a + b, 0);
}

public static int Product(this IEnumerable<int> seq)
{
    return seq.Aggregate((a, b) => a * b, 1);
}

Now we can easily call Sum and Product as extension methods on lists of integers.

Of course, all of this is already built-in into LINQ.

Pros

It reduces code duplication.

Much more concise and much less boiler-plate code.

Every function has explicit inputs and outputs.

It is easy to use, test and reason about.

Cons

Maybe the function signature of Aggregate looks a little complex. However, I can’t think of anything else.

Conclusion

If we have several implementations of an algorithm following the same structure, but with some different individual steps the Template Method design pattern seems to be a good fit.

To keep things clean and in order to reduce duplicate code the Template Method pattern helps to generalize common parts.

We looked at an example and compared the application of the Template Method pattern to an alternative approach adopted from functional programming.

This alternative approach made use of higher-order functions.

Even though this example is kind of simple and bold it demonstrates that the functional way may lead to cleaner and more maintainable code.