BLOG
26 May 2022

Design Patterns: Templates and Trust Issue Patterns

tech

This is the seventh post in the “Design Patterns” series.  The previous one was about the chain pattern and can be found at: https://softwarehut.com/blog/tech/design-patterns-chain.

In this instalment I want to talk about the template pattern. This pattern is being used when you want to be sure that your code, very often a based class logic, will be used correctly. Yes, I’m aware that that’s a common objective - we always want that. This case focusses on a situation when a based class has child classes and a method that can – and should - be overwritten in a specific way.

We don’t want to trust users to do it correctly. Instead, what we want is to create a code where they have no other way of reaching its objective.

The code presented in this post can be found at: https://github.com/Nivo1985/net_patterns. You’ll find that the projects in the solution correspond with pattern names.

The goal

We are going to create a short console application. In it, we are going to have a parent base class. This class is going to have a virtual method. But to use this method correctly, the core that overrides it must call its based definition.

The goal is to design a class in such a way that its elements can’t be used incorrectly. Let’s see if this can do done.

The old way

First let’s start with a code we all know and use. Please keep in mind that this piece of code is going to work fine, most of the time. My goal is to show you how to improve this behaviour.

1 2 3 4 5 6 7 8 9 10 public class OldParent { private bool _isWorkingFlag = false; public virtual void Start() => StartProcess(); public string GetStatus() => _isWorkingFlag ? "Working" : "Stopped"; private void StartProcess() => _isWorkingFlag = true; }

This is the base class we are working with, which as you can see, is rather easy. As users of this class, we should only concern ourselves with public methods. First, there is a Start method. What that does is assume the inner private method, whereby the method keeps our object working. Without that, we may as well should consider object stopped. In fact, we are considering starting an object to be required by a step-of-code working correctly.

Secondly, the public method reverts the status of an object. This offers us information if an object is working or has stopped.

Now, we will see what approaches we have when it comes to ­inheriting from this class.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class GoodChild : OldParent { public override void Start() { base.Start(); Console.WriteLine("Good child acting"); } } public class BedChild : OldParent { public override void Start() { Console.WriteLine("Good child acting"); }

We have two classes inheriting from our base class. Both are overriding the virtual Start method. That is perfectly fine, which is why this method was made virtual. The problem is that one of the classes does it correctly, whereas the second less so. The GoodChild class, meanwhile, does it right. In this class, in the Start method, we will start by calling the Start method from the based class. This is done “by the book”. But before we get super happy, we need to look at the second class.

The BadChild class also inherits from the OldParent class. It also overrides the base Start method. The problem is that is does it in a slightly unusual way.  The reason for that is that there is no call to base in the Start method. It validates the way base class is intended to be used but is nevertheless 100% code-legal. So, we won’t get any information about it.

1 2 3 4 5 6 7 8 9 10 void OldWay() { var goodChild = new GoodChild(); goodChild.Start(); Console.WriteLine(goodChild.GetStatus()); var badChild = new BedChild(); badChild.Start(); Console.WriteLine(badChild.GetStatus()); }

If we run this using the ‘Old Way’ method, we will see that results are showing that there is something wrong with the implementation. In the first example, it is working, whereas in the second example, the case is less certain. This is because the base Start method was not called.

I wonder if there is something we can do about it…

The new way

Sure, there is!

We can use the Template pattern and turn the tables around on this situation. What do I mean by turning the tables? I mean changing the way we design the class in such a situation. We are going to design them in a different way. In the previous example, we simply created a virtual method and was hoping that it will get overwritten in the correct way. Of course, we need to remember to be reasonable. After all, as the saying goes, ‘hope is not the best design strategy’. By using the template pattern, we will try to solve this issue.

First, we need to look the NewParent class. The class underwent quite a revolution. Not when it comes to the code size, but to the approach itself. Now the class is abstract and has adopted an abstract method of After plus a virtual method of Before.

Now, what is up with that? There is an easy answer to that one. In the new approach, the Start method remains in the base class and remains intact. Thanks to this approach, we know that its behaviour won’t get altered. We can make sure that the code in the After method therefore always gets called after the process had been restarted. If there are some operations, you need to carry those out before that is implemented prior to the Before method.

Please keep in mind that one module is mandatory whereas the second is optional. For that reason, I have unearthed why one of these factors is abstract, while the second is virtual.

What we did in the Start method was to create a template of behaviours required for the process to work correctly. Now that the class inheriting from NewParent will only provide the behaviour for Before and After. The running of the code remains within the base class, while the well-designed template process remains in its Start method. Thanks to this approach, there is no way to omit the call of the StartProcess method in object lifecycle.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public abstract class NewParent { private bool _isWorkingFlag = false; public void Start() { Before(); StartProcess(); After(); } protected virtual void Before() { } protected abstract void After(); public string GetStatus() => _isWorkingFlag ? "Working" : "Stopped"; private void StartProcess() => _isWorkingFlag = true; }

Some of you may have accurately noticed that the Before method is being called before the StartProcess. This is true, but Before was added for convenience, as well as to ensure the solution is complete. To be safe, it has no inner code inside the base class method implementation. In this case, there is no restriction to override it in a child class. And, if we don’t trust users at all, we can remove it. If we were to the opt for NewParent, it will be identical to OldParent, when it comes to usability.

1 2 3 4 5 6 7 8 9 10 11 12 13 public class NewChild : NewParent { protected override void Before() { base.Before(); Console.WriteLine("Actions before"); } protected override void After() { Console.WriteLine("Actions after"); } }

The NewChild class looks very much like you would expect. Improvement is in the fact that now I can’t implement it wrong. The fact that After is an abstract method forces us to have an implementation of it in the derived class.

1 2 3 4 5 6 void NewWay() { var onlyChild = new NewChild(); onlyChild.Start(); Console.WriteLine(onlyChild.GetStatus()); }

Using of the NewChild class is very straightforward. All we need to do is to create an object and call for the Start method.

As we can see both methods got called and the object is working. It was done in a way that is safe for each class inheriting from the base class.

Summary

The base class design has changed. Now, we are exceptionally safer, as compared to how the class will be used, and in the way it was originally designed. This was done by using the template pattern, which can come especially in handy in the situations we want to strengthen our code when used in its originally intended manner.

As every single pattern it should be used in cases where there will be value generated. Have you ever used it? Let me know at karol.rogowski@softwarehut.com.



Author
Karol Rogowski
Head Of Engineering

Working in IT since 2009. Currently working as Head of Engineering at SoftwareHut and as an academic teacher at Białystok Technical University. Co-founder of meet.js Białystok. Book and articles author. Father, husband, huge H.P. Lovecraft fan and terrible poker player.