Template Pattern C#

Contents

What Is Template Method Pattern?

The Template Method pattern in C# enables algorithms to defer certain steps to subclasses. The structure of the algorithm does not change, but small well-defined parts of its operation are handled elsewhere.

Template Methods are very useful in conjunction with the Strategy pattern. Any system that wishes to defer primitive operations to other classes will benefit from this pattern. In C#, it is used extensively through predefined interfaces.

The Template Method pattern defines the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.

Template Method Pattern C# Example

Let’s start with the problem statement,

Let say you are creating a library of algorithms for C# developers that contain all type of sorting algorithm. One of the algorithms would be Merge Sort like this:

 1 using System.Collections.Generic;
 2 using System.Linq;
 3 
 4 public class MergeSort
 5 {
 6     public List<int> Sort(List<int> unsorted)
 7     {
 8         if (unsorted.Count <= 1)
 9             return unsorted;
10 
11         List<int> left = new List<int>();
12         List<int> right = new List<int>();
13 
14         int middle = unsorted.Count / 2;
15         for (int i = 0; i < middle; i++)  //Dividing the unsorted list
16         {
17             left.Add(unsorted[i]);
18         }
19         for (int i = middle; i < unsorted.Count; i++)
20         {
21             right.Add(unsorted[i]);
22         }
23 
24         left = Sort(left);
25         right = Sort(right);
26         return Merge(left, right);
27     }
28 
29     private List<int> Merge(List<int> left, List<int> right)
30     {
31         List<int> result = new List<int>();
32 
33         while (left.Count > 0 || right.Count > 0)
34         {
35             if (left.Count > 0 && right.Count > 0)
36             {
37                 //Comparing First two elements to see which is smaller
38                 if (left.First() <= right.First())  
39                 {
40                     result.Add(left.First());
41                     //Rest of the list minus the first element
42                     left.Remove(left.First());      
43                 }
44                 else
45                 {
46                     result.Add(right.First());
47                     right.Remove(right.First());
48                 }
49             }
50             else if (left.Count > 0)
51             {
52                 result.Add(left.First());
53                 left.Remove(left.First());
54             }
55             else if (right.Count > 0)
56             {
57                 result.Add(right.First());
58 
59                 right.Remove(right.First());
60             }
61         }
62         return result;
63     }
64 }

Now you don’t have to know the code for the Merge Sort, it’s only going to be here to prove a point.

Let’s see how clients will use our above class,

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 
 5 namespace example1
 6 {
 7     class Program
 8     {
 9         static void Main(string[] args)
10         {
11             Random _rand = new Random(1024);
12 
13             //Generate list of random int variables
14             var unsorted = Enumerable.Range(0, 10)
15                                     .Select(r => _rand.Next(100))
16                                     .ToList();
17             List<int> sorted;
18 
19 
20             Console.WriteLine("Original array elements:");
21             foreach (var item in unsorted)
22             {
23                 Console.Write(item + " ");
24             }
25             Console.WriteLine();
26 
27             var algorithm = new MergeSort();
28             sorted = algorithm.Sort(unsorted);
29 
30             Console.WriteLine("Sorted array elements: ");
31             foreach (var item in sorted)
32             {
33                 Console.Write(item + " ");
34             }
35             Console.WriteLine();
36             Console.ReadLine();
37         }        
38     }
39 }

Everything looks cool in the above program right! If we run the above program we will get the following output.

1 Original array elements:
2 68 27 53 12 52 43 71 16 66 87
3 Sorted array elements:
4 12 16 27 43 52 53 66 68 71 87

Now,

Here’s my problem, it only stores int.

What if our clients want to use a different data type like float or string or any user-defined type like below one, a Person type.

 1 public class Person
 2 {
 3     public Person(string name, int age)
 4     {
 5         Name = name;
 6         Age = age;
 7     }
 8 
 9     public string Name { get; set; }
10     public int Age { get; set; }    
11 
12     public override string ToString()
13     {
14         return $"Name: {Name}\tAge: {Age}";
15     }
16 }

Also, what if I have to sort the list of person based on its age or name.

One thing we can do is make our MergeSort algorithm generic like this,

Note: You can learn more about Generic Types in my blog post Generics In C#.

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 
 5 namespace example2
 6 {
 7     public class MergeSort<T>
 8     {
 9         public List<T> Sort(List<T> unsorted)
10         {
11             if (unsorted.Count <= 1)
12                 return unsorted;
13 
14             List<T> left = new List<T>();
15             List<T> right = new List<T>();
16 
17             int middle = unsorted.Count / 2;
18             for (int i = 0; i < middle; i++)  //Dividing the unsorted list
19             {
20                 left.Add(unsorted[i]);
21             }
22             for (int i = middle; i < unsorted.Count; i++)
23             {
24                 right.Add(unsorted[i]);
25             }
26 
27             left = Sort(left);
28             right = Sort(right);
29             return Merge(left, right);
30         }
31 
32         private List<T> Merge(List<T> left, List<T> right)
33         {
34             List<T> result = new List<T>();
35 
36             while (left.Count > 0 || right.Count > 0)
37             {
38                 if (left.Count > 0 && right.Count > 0)
39                 {
40                     //Comparing First two elements to see which is smaller
41                     if (left.First() <= right.First())
42                     {
43                         result.Add(left.First());
44                         //Rest of the list minus the first element
45                         left.Remove(left.First());      
46                     }
47                     else
48                     {
49                         result.Add(right.First());
50                         right.Remove(right.First());
51                     }
52                 }
53                 else if (left.Count > 0)
54                 {
55                     result.Add(left.First());
56                     left.Remove(left.First());
57                 }
58                 else if (right.Count > 0)
59                 {
60                     result.Add(right.First());
61 
62                     right.Remove(right.First());
63                 }
64             }
65             return result;
66         }
67     }
68 }

After we make this adjustment you will find that there would be a compile-time error

So, how to solve this?

As I mentioned earlier, In C#, the template method is used extensively through predefined interfaces, one such interface is IComparable.

In the above sort method, the compare operations can replace <= operator with the primitive operation CompareTo, as in:

1 if (left.First().CompareTo(right.First()) <= 0)  

CompareTo is a method that returns 1 if a>b, 0 if a==b, and -1 if a<b.

It is defined in the IComparable interface, which many predefined C# types already implement. For example, CompareTo is available for integers and strings. Because our sort methods are meant to be general, we declare that the item types must implement the interface using:

1 public class MergeSort<T> : where T : IComparable<T>

The where clause puts a constraint on the type <T> by saying it must implement IComparable. Any class can do so if it provides a CompareTo method. So, inside a Person class, we have to implement the IComparable interface like this:

 1 public class Person : IComparable<Person> 
 2 {
 3     ...
 4     ...
 5 
 6     public int CompareTo(Person other)
 7     {
 8         // Comparison based on names
 9         return Name.CompareTo(other.Name);
10     }    
11 }

But how is CompareTo defined in terms of CompareTo? Well, we are looking at names, so the name will presumably be a string. CompareTo is defined for strings so that is what will be called. The check for the type of the object is necessary because the implementation of CompareTo must match its interface, which is defined on the object.

Let’s see how clients can use our new update merge sort class,

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 
 5 namespace example2
 6 {
 7     class Program
 8     {
 9         public class Person : IComparable<Person>
10         {
11             public Person(string name, int age)
12             {
13                 Name = name;
14                 Age = age;
15             }
16 
17             public string Name { get; set; }
18             public int Age { get; set; }
19 
20             public int CompareTo(Person other)
21             {
22                 return Name.CompareTo(other.Name);
23             }
24 
25             public override string ToString()
26             {
27                 return $"Name: {Name}\tAge: {Age}";
28             }
29         }
30 
31         static void Main(string[] args)
32         {
33             var names = new List<string>()
34             {
35                 "Tamra Grist"       ,
36                 "Bennie Sweatt"     ,
37                 "Misha Mattei"      ,
38                 "Mable Lampkins"    ,
39                 "Kaley Gervasi"     ,
40                 "Nettie Horace"     ,
41                 "Cassidy Broxton"   ,
42                 "January Berk"      ,
43                 "Michele Barga"     ,
44                 "Arden Emig"        ,
45             };
46 
47             Random _rand = new Random(1024);
48 
49             //Generate list of random int variables
50             var unsorted = Enumerable.Range(0, 10)
51                                     .Select(r => new Person(names[r], _rand.Next(100)))
52                                     .ToList();
53             List<Person> sorted;
54 
55 
56             Console.WriteLine("Original array elements:");
57             foreach (var item in unsorted)
58             {
59                 Console.WriteLine(item);
60             }
61             Console.WriteLine();
62 
63             var algorithm = new MergeSort<Person>();
64             sorted = algorithm.Sort(unsorted);
65 
66             Console.WriteLine("Sorted array elements: ");
67             foreach (var item in sorted)
68             {
69                 Console.WriteLine(item);
70             }
71             Console.WriteLine();
72             Console.ReadLine();
73         }
74     }
75 }

If we run the above program we will get the following output:

 1 Original array elements:
 2 Name: Tamra Grist       Age: 68
 3 Name: Bennie Sweatt     Age: 27
 4 Name: Misha Mattei      Age: 53
 5 Name: Mable Lampkins    Age: 12
 6 Name: Kaley Gervasi     Age: 52
 7 Name: Nettie Horace     Age: 43
 8 Name: Cassidy Broxton   Age: 71
 9 Name: January Berk      Age: 16
10 Name: Michele Barga     Age: 66
11 Name: Arden Emig        Age: 87
12 
13 Sorted array elements:
14 Name: Arden Emig        Age: 87
15 Name: Bennie Sweatt     Age: 27
16 Name: Cassidy Broxton   Age: 71
17 Name: January Berk      Age: 16
18 Name: Kaley Gervasi     Age: 52
19 Name: Mable Lampkins    Age: 12
20 Name: Michele Barga     Age: 66
21 Name: Misha Mattei      Age: 53
22 Name: Nettie Horace     Age: 43
23 Name: Tamra Grist       Age: 68

When a Template Method uses an interface for specifying the primitive operations, the classes must implement those operations. It is also possible to define IPrimitives as an abstract class and to provide default behavior for any of the operations, leaving the subclass to override as many operations as it wishes. Methods that have defaults are called hook operations, and they often do nothing by default.

Where To Apply Template Method Pattern?

  • When you need to implement the invariant parts of an algorithm once and leave it up to subclasses to implement the behavior that can vary.

  • When common behavior among subclasses should be factored and localized in a common class to avoid code duplication. This is a good example of “refactoring to generalize” as described by Opdyke and Johnson in their research paper Creating Abstract Superclasses by Refactoring. You first identify the differences in the existing code and then separate the differences into new operations. Finally, you replace the differing code with a template method that calls one of these new operations.

  • When you need to control subclasses extensions. You can define a template method that calls “hook” operations at specific points, thereby permitting extensions only at those points.

The Template Method pattern is more like the Strategy pattern in that it is algorithm-based. The steps of the algorithm are specified in the Template Method and some are deferred to domain classes.

Note: You can download the complete solution demo from my github repository.

Further Reading

  • Strategy Pattern C# - The Strategy pattern in C# lets the algorithm vary independently from clients that use it. The Strategy pattern enables a client to choose which algorithm to use from a family of algorithms and gives it a simple way to access it.

  • Template Method Pattern. Can we do better? by Leif Battermann - In this post, Leif compare 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 in some situations.

  • Curiously recurring template pattern by Guillermo Subirán - In this post, Guillermo explains the curiously recurring template pattern (CRTP) which is an idiom in C++ in which class X derives from a class template instantiation using X itself as a template argument. More generally it is known as F-bound polymorphism, and it is a form of F-bounded quantification. The idea of CRTP is similar to the template method pattern we discussed in this post.

Template Pattern C#
Share this

Subscribe to Code with Shadman