Table of Contents
- Course Overview
- Introduction to Template Method Pattern
- What is the Template Method?
- Examples of typical problems
- What problem does the Template Method solve?
- How to apply the Template Method Pattern (refactoring steps)
- Demo: Application of the Template Method Pattern
- 10.1 PanFood abstract base class
- 10.2 Generic PanFoodServiceBase class — The Template Method
- 10.3 Concrete implementation: PieBakingService
- 10.4 Concrete implementation: PizzaBakingService
- 10.5 Case with conditional behavior: ColdVeggiePizza
- 10.6 Logging utility: LoggerAdapter
- 10.7 Unit tests
- 12.1 Factory Method
- 12.2 Strategy Pattern
- 12.3 Rules Engine Pattern
1. Course Overview
This course introduces the Template Method Design Pattern in the context of C# / .NET development. The Template Method is a fascinating pattern because it represents an individual tool that one can add to their toolbox as a software developer. These patterns don’t take long to introduce, but they can take a lot of practice to truly master.
Course objectives
At the end of this course, you will be able to:
- Recognize situations where the Template Method pattern is appropriate
- Understand what problem this pattern is designed to solve
- Identify the software design principles that apply to this pattern
- Apply the Template Method pattern in different ways in your applications
- Distinguish other similar design patterns and know when to use them
Major Topics Covered
| Subject | Description |
|---|---|
| Targeted problem | What problem does the Template Method solve? |
| Principles | What software design principles apply? |
| Application | How to apply the pattern concretely |
| Legacy | How it improves even basic inheritance |
| Related Patterns | What other patterns are similar |
2. Introduction to Template Method Pattern
The Template Method is an excellent pattern for:
- Reduce duplicate code between multiple similar classes
- Impose design constraints by ensuring that an algorithm is always executed in the correct order
- Produce extensible frameworks and plugins — you have probably already used this pattern without knowing it, in frameworks that you use daily
Basic questions to understand
- What is the Template Method pattern?
- Why does it have this name?
- What types of problems does it solve and how to recognize them?
- How is it structured? What are its different parts and how do they interact?
- How to apply it in real code?
- How to recognize similar patterns and know when to prefer them to the Template Method?
3. What is the Template Method?
“A template method is a method in a superclass that defines the skeleton of an operation or algorithm in terms of higher level steps. Subclasses then implement these steps.” — Gang of Four, Design Patterns, 1994
Simply put: It’s a way to lock an algorithm’s steps and their sequence, while allowing the details of each step to vary via inheritance.
Basic characteristics
- Category: Behavioral design pattern
- Purpose: Organize application behavior
- Frequently used: Eliminate duplication and provide extension points
- Fundamental technique: Code reuse
Origin and relevance
This pattern is taken directly from the Gang of Four (GoF) book Design Patterns, published in 1994, and it remains just as relevant today. Template Methods are particularly useful for class libraries, because they provide a mechanism for factoring common code across these libraries.
Framework authors can use this pattern to provide specific extensibility points while maintaining control over the overall process.
4. Examples of typical problems
The Template Method pattern can be applied in many scenarios. Here are the most common cases:
4.1 Board game management
A system that tracks the progress of a game and determines if someone has won. If you implement this logic for checkers, then for chess, then for Battleship, you will notice a lot of duplication in logic that could be rolled up into a common class.
4.2 Cooking recipes
Following certain types of recipes may involve similar steps:
- Preparation
- Cooking
- Presentation
4.3 User interface frameworks
Many interface frameworks, especially those with controls, use this pattern. The most notable example is ASP.NET Web Forms (the old ASP.NET paradigm), which heavily used the Template Method. Each control and page had a series of events in its lifecycle that developers could extend, but they did not have control over the lifecycle itself.
4.4 ETL (Extract, Transform, Load) Process
ETL processes have an obvious candidate for a simple Template Method, written right into their name:
- Extract — Extract data
- Transform — Transform data
- Load — Load data
4.5 Document processing
A system designed to load and work with a certain type of document (such as a PDF). When you have to do the same thing for spreadsheets and then for images, you notice that the general process is the same across each type of application.
Illustrated example: Document processing application
Consider an application that works with documents of various formats. The basic steps could be standard, whether it’s a PDF, CSV, XML or another format:
Application.ProcessDocument(document):
1. OpenDocument(document)
2. ParseDocument(document)
3. ProcessData(document)
4. CloseDocument(document)
The ProcessDocument method acts as a generic method in the Application class. It calls several other methods. The specific details of what to do with each format vary with each concrete implementation, but the high-level process is dictated by this template method.
5. What problem does the Template Method solve?
There are several typical cases where the Template Method is appropriate:
5.1 Locking a process while allowing extensibility
The Template Method is useful for locking a process and ensuring that all clients follow this process, while allowing them to customize certain steps. This is particularly relevant for:
- Impose a recipe or algorithm
- Ensure initialization is always done before action
- Ensure cleanup is always done after the action
5.2 Generalize duplicate behavior
With a little refactoring, this duplicate process flow behavior can be rolled back into a template method in a base class. If you discover multiple classes that have similar methods, the Template Method can extract and centralize the common logic.
5.3 Create extension points in a framework
If you are building a framework and want to ensure that your clients can extend it in the future, this approach is excellent for creating extension points while maintaining control. If you have ever worked with a framework that supported a plugin or control pattern that required you to inherit from a particular type and override certain methods called during the lifecycle of the page, you have been a consumer of that pattern.
5.4 The problem with simple inheritance
The Template Method can also be used in very simple cases where you want to use inheritance, but you need to ensure that the basic functionality is preserved.
By default, child types can override virtual members of base types. When they do, the new method is called instead of the original. If the original method needs to be called, and if it specifically needs to be called before or after the child type’s additional behavior, there is no built-in way to enforce that behavior.
This can lead to:
- Educate developers via documentation
- Comments in the code
- Other techniques outside of the code itself
All of these ways are error prone and easily overlooked. It would be better if our design could enforce the desired behavior.
6. Demo: Problems that the Template Method can solve
Let’s imagine that we run a bakery that offers pastries, such as pies.
6.1 The Pie Baking Service (PieBakingService)
The PreparePie method in PieBakingService was once long and complex, but over time it has been refactored so that each discrete step has its own method. Here is the process of making a pie:
Pie steps:
- Instantiate a new pie
- Prepare the crust (roll the dough and press it into the pan)
- Add the filling
- Cover (add lattice top)
- Cook 45 minutes
- Cut into 6 pieces
- Flip the prepared pie
Output when running the test:
Rolling out crust and pressing into pie pan
Adding pie filling
Adding lattice top
Baking for 45 minutes
Cutting into 6 slices.
6.2 The pizza baking service (PizzaBakingService)
The same process adapted for pizzas:
Pizza steps:
- Instantiate a new pizza
- Prepare the crust (stretch and throw the dough by hand)
- Add toppings
- Cook for 15 minutes
- Cut into 8 pieces
- Flip the prepared pizza
Output when running the test:
Rolling out and hand tossing the dough
Adding pizza toppings
Baking for 15 minutes
Cutting into 8 slices.
6.3 Duplication issue highlighted
Looking at these two methods side by side:
| Step | PieBakingService | PizzaBakingService |
|---|---|---|
| 1 | new Pie() | new Pizza() |
| 2 | PrepareCrust() | PrepareCrust() |
| 3 | AddFilling() | AddToppings() |
| 4 | Cover() | (not applicable) |
| 5 | Bake() | Bake() |
| 6 | Slice(6) | Slice(8) |
| 7 | return pie | return pizza |
The problem: If we want to add a third baked item, we duplicate this process again. The overall structure is identical, only the details vary. This is exactly the type of situation where the Template Method pattern is the ideal solution.
7. Structure of the Template Method Pattern
The Template Method pattern gets its name from a non-virtual method in a parent or base class that calls other virtual or abstract methods in that same parent class.
Fundamental rule
Child classes override methods where they want to customize the behavior, but they cannot touch the template method itself.
Structure components
AbstractClass (Classe abstraite)
├── TemplateMethod() ← Non-virtuelle, NON surchargeable
│ ├── appelle PrimitiveOperation1()
│ ├── appelle PrimitiveOperation2()
│ ├── appelle Hook()
│ └── appelle ConcreteOperation()
│
├── PrimitiveOperation1() ← abstract : DOIT être implémentée par les enfants
├── PrimitiveOperation2() ← abstract : DOIT être implémentée par les enfants
├── Hook() ← virtual : comportement par défaut vide (hook)
└── ConcreteOperation() ← Implémentation commune partagée
ConcreteClass (Classe concrète héritant de AbstractClass)
├── PrimitiveOperation1() ← Implémentation spécifique
├── PrimitiveOperation2() ← Implémentation spécifique
└── Hook() ← Surchargée si nécessaire (optionnel)
Method types in abstract class
| Type | Modifier | Description |
|---|---|---|
| Template Method | (non-virtual) | Algorithm skeleton, cannot be overloaded |
| Primitive operations | abstract | Must be implemented by subclasses |
| Hook methods | virtual (empty body) | Optional, subclasses can override them |
| Concrete operations | (non-virtual) | Common implementation, shared by all subclasses |
Is the abstract class required?
No, this is not always required for the base class to be abstract, although it is a fairly common approach when:
- You have several different concrete operations
- Generic operation cannot work standalone
- It can only be used as a template for other specific concrete implementations
But you will also find the Template Method used in standard (non-abstract) base classes.
8. How to work with the Template Method Pattern
When working with a Template Method, here are the rules to follow:
- Inherit from the class containing the method
- Implement one or more specific virtual steps exposed by the base type
- Do not implement or override the template method itself — leave this alone
- Extend the default behavior of the base type using hooks if available and necessary
8.1 The Hollywood Principle
“Don’t call us; we’ll call you.” (“Don’t call us, we’ll call you.”)
This principle corresponds perfectly to the operation of the Template Method. When working with a class that uses this pattern:
- You are not calling the template method
- It calls you when you override the other methods it invokes
Instead of being behind the wheel and determining the exact control flow of the operation, you let the template method take the controls and simply provide it with the necessary details for specific steps in the process. The template method determines when and if it will call these steps.
[Vous] ←────────────────────────────────────────────
│ Vous héritez et implémentez les étapes │
│ mais vous NE contrôlez PAS le flux │
▼ │
[TemplateMethod] ──── appelle ──→ [VotreÉtape1()] ────┘
──── appelle ──→ [VotreÉtape2()]
──── appelle ──→ [VotreHook()]
9. How to apply the Template Method Pattern (refactoring steps)
If you have existing code that could benefit from the Template Method pattern, here are the steps to follow to refactor it:
Step 1: Ensure you have tests
Before any refactoring, check that you have tests that verify the current behavior.
Step 2: Extract steps into methods
Extract the methods for each of the common steps. Do this in two methods that share similar steps, and try to have the methods as close as possible in terms of names and parameters.
If you have more than two sets of processes to combine, start with just two of them.
Step 3: Go back to the base class
Once the original method is simplified to the point of simply calling other methods to perform the steps, move it back into the base class.
Step 4: Do not make the template method virtual
If you need to create a new base class just for this, remember: do not make this template method virtual.
Step 5: Create the methods for the individual steps
Create methods in the base class for individual steps:
- If they are the same between children → move the implementation to the base class
- If they vary → make the base class abstract and have this abstract method implemented by the child classes
Step 6: Add hooks (optional)
Determine whether you expect future classes to need to incorporate additional behavior into the process. If so, add additional virtual methods to the base class and make sure to call them from the appropriate place in the template method. The implementation of these methods should do nothing by default.
Summary of steps
1. Écrire/vérifier les tests
2. Extraire les étapes communes en méthodes
3. Aligner les noms des méthodes entre les deux classes
4. Créer la classe de base (abstract si nécessaire)
5. Déplacer la méthode template dans la classe de base (NON virtual)
6. Déplacer les étapes communes dans la classe de base
7. Déclarer les étapes variables comme abstract
8. Ajouter des hooks (virtual, corps vide) si nécessaire
10. Demo: Application of the Template Method Pattern
Here is the complete implementation of the refactoring of our bakery system to use the Template Method pattern.
10.1 PanFood abstract base class
The PanFood class is the base template class for all foods cooked in a pan:
namespace DesignPatternsInCSharp.TemplateMethod
{
public abstract class PanFood
{
public bool RequiresBaking { get; set; } = true;
}
}
Key points:
- Class
abstract— cannot be instantiated directly - Property
RequiresBakingwith default valuetrue - This property allows conditional logic in the template method
10.2 PanFoodServiceBase generic class — The Template Method
This is the heart of the pattern. The PanFoodServiceBase<T> class is generic and constrains T to be a PanFood with a parameterless constructor:
namespace DesignPatternsInCSharp.TemplateMethod
{
public abstract class PanFoodServiceBase<T> where T : PanFood, new()
{
protected readonly LoggerAdapter _logger;
protected T _item;
public PanFoodServiceBase(LoggerAdapter logger)
{
_logger = logger;
}
// *** LE TEMPLATE METHOD ***
public T Prepare()
{
_item = new T();
PrepareCrust();
AddToppings();
Cover();
if (_item.RequiresBaking)
{
Bake();
}
Slice();
return _item;
}
protected abstract void PrepareCrust();
protected abstract void AddToppings();
protected virtual void Bake()
{
_logger.Log("Bake the item.");
}
protected abstract void Slice();
// Hook method — ne fait rien par défaut
protected virtual void Cover()
{
// does nothing by default
}
}
}
Detailed analysis of the Template Method Prepare()
Prepare()
│
├─→ new T() ← Crée une instance via le paramètre générique (Factory Method implicite)
├─→ PrepareCrust() ← abstract : chaque produit prépare sa croûte différemment
├─→ AddToppings() ← abstract : chaque produit a ses propres garnitures
├─→ Cover() ← virtual hook : vide par défaut, surchargeable si besoin
├─→ if (RequiresBaking) → Bake() ← Logique conditionnelle basée sur la propriété du modèle
├─→ Slice() ← abstract : chaque produit est découpé différemment
└─→ return _item ← Retourne le produit complètement préparé
Design highlights
| Method | Type | Rationale |
|---|---|---|
Prepare() | public (non-virtual) | This is the template method — cannot be overridden |
PrepareCrust() | abstract | Varies between pie and pizza — must be implemented |
AddToppings() | abstract | Varies between pie and pizza — must be implemented |
Bake() | virtual | Default implementation provided, overloadable |
Slice() | abstract | Varies between pie and pizza — must be implemented |
Cover() | virtual (empty body) | Hook — does nothing by default |
The generic constraint where T: PanFood, new() guarantees:
Tinherits fromPanFood(so has theRequiresBakingproperty)Thas a parameterless constructor (to be able to donew T())
10.3 Concrete implementation: PieBakingService
namespace DesignPatternsInCSharp.TemplateMethod
{
public class Pie : PanFood { }
public class PieBakingService : PanFoodServiceBase<Pie>
{
public PieBakingService(LoggerAdapter logger) : base(logger)
{
}
protected override void PrepareCrust()
{
_logger.Log("Rolling out crust and pressing into pie pan");
}
protected override void AddToppings()
{
_logger.Log("Adding pie filling");
}
protected override void Cover()
{
_logger.Log("Adding lattice top");
}
protected override void Bake()
{
_logger.Log("Baking for 45 minutes");
}
protected override void Slice()
{
_logger.Log("Cutting into 6 slices.");
}
}
}
Key points:
Pieinherits fromPanFood— very simple template class, no additional behaviorPieBakingServiceinherits fromPanFoodServiceBase<Pie>Cover()overload — pie has a lattice top, unlike pizzaBake()overload — 45 minutes for a pie- The
RequiresBakingofPieremainstrue(default value ofPanFood)
10.4 Concrete implementation: PizzaBakingService
using System.Collections.Generic;
namespace DesignPatternsInCSharp.TemplateMethod
{
public class Pizza : PanFood
{
public string CrustType { get; set; } = "no crust";
public int NumSlices { get; set; } = 1;
public List<string> Toppings { get; private set; } = new List<string>();
public bool WasBaked { get; set; }
}
}
The Pizza class is richer than Pie — it contains domain-specific properties:
CrustType: crust typeNumSlices: number of slicesToppings: list of toppingsWasBaked: baking indicator
namespace DesignPatternsInCSharp.TemplateMethod
{
public class PizzaBakingService : PanFoodServiceBase<Pizza>
{
public PizzaBakingService(LoggerAdapter logger) : base(logger)
{
}
protected override void PrepareCrust()
{
_logger.Log("Rolling out and hand tossing the dough");
_item.CrustType = "Thin";
}
protected override void AddToppings()
{
_logger.Log("Adding pizza toppings");
_item.Toppings.Add("Pepperoni");
_item.Toppings.Add("Sausage");
}
protected override void Bake()
{
_logger.Log("Baking for 15 minutes");
_item.WasBaked = true;
}
protected override void Slice()
{
_logger.Log("Cutting into 8 slices.");
_item.NumSlices = 8;
}
}
}
Key points:
PizzaBakingServicedoes not overrideCover()— the pizza is not covered (the hook method does nothing)- Each method also updates the state of the
_itemobject in addition to logging Bake()overload — only 15 minutes for a pizza
10.5 Case with conditional behavior: ColdVeggiePizza
This case illustrates the handling of conditional behavior in the Template Method — the ColdVeggiePizza does not require cooking.
namespace DesignPatternsInCSharp.TemplateMethod
{
public class ColdVeggiePizza : PanFood
{
public ColdVeggiePizza()
{
base.RequiresBaking = false;
}
}
}
Important point: The RequiresBaking property is set to false in the model constructor, not in the service. This tells the template method not to call Bake().
namespace DesignPatternsInCSharp.TemplateMethod
{
public class ColdVeggiePizzaBakingService : PanFoodServiceBase<ColdVeggiePizza>
{
public ColdVeggiePizzaBakingService(LoggerAdapter logger) : base(logger)
{
}
protected override void AddToppings()
{
_logger.Log("Add cream cheese, peppers, and veggies");
}
// Il y a plusieurs façons de gérer un comportement optionnel comme Bake() :
// 1. Surcharger la valeur par défaut et ne rien faire
// 2. Ajouter une logique conditionnelle dans la méthode template de base
// 3. Implémenter une méthode hook "ne rien faire" dans le template de base ;
// ne surcharger que si vous avez besoin de faire quelque chose (voir Cover)
//
// protected override void Bake()
// {
// // surcharger le comportement par défaut
// }
protected override void PrepareCrust()
{
_logger.Log("Rolling out dough and press into pan");
}
protected override void Slice()
{
_logger.Log("Slice into squares");
}
}
}
Three approaches to handling optional behavior
The comment in the code illustrates three strategies for handling behavior that does not apply to all subclasses:
| Approach | Description | Disadvantages |
|---|---|---|
| 1. Overload and do nothing | Override the method and leave the body empty | Violates Liskov Substitution, unexpected behavior |
| 2. Conditional logic in the template | if (RequiresBaking) in Prepare() | Coupling in base class |
| 3. Hook method empty | virtual method with empty body in the base | Recommended approach — used for Cover() |
The solution chosen here is the combination of approach 2 and 3:
- For
Bake(): conditional logicif (_item.RequiresBaking)inPrepare() - For
Cover(): empty hook method in the base class
10.6 Logging utility: LoggerAdapter
The LoggerAdapter is a simple utility class used in all examples to capture and display logging messages:
using System;
using System.Collections.Generic;
namespace DesignPatternsInCSharp.TemplateMethod
{
public class LoggerAdapter
{
private List<string> _messages = new List<string>();
public void Log(string message)
{
_messages.Add(message);
}
public string Dump()
{
return String.Join(Environment.NewLine, _messages);
}
}
}
Role: Accumulates log messages into a list and can dump them at once as a multiline string. Very useful in testing to verify that the correct sequencing of steps has taken place.
10.7 Unit tests
Tests validate the behavior of each service. Here are the tests for PieBakingService:
using Xunit;
using Xunit.Abstractions;
namespace DesignPatternsInCSharp.TemplateMethod
{
public class PieBakingServicePreparePie
{
private readonly ITestOutputHelper _output;
public PieBakingServicePreparePie(ITestOutputHelper output)
{
_output = output;
}
[Fact]
public void ReturnsAPie()
{
var logger = new LoggerAdapter();
var service = new PieBakingService(logger);
var pie = service.Prepare();
Assert.NotNull(pie);
_output.WriteLine(logger.Dump());
}
}
}
Tests for PizzaBakingService check specific properties of the pizza:
using System;
using Xunit;
using Xunit.Abstractions;
namespace DesignPatternsInCSharp.TemplateMethod
{
public class PizzaBakingServicePreparePizza : IDisposable
{
private readonly ITestOutputHelper _output;
private readonly LoggerAdapter _logger = new LoggerAdapter();
private readonly PizzaBakingService _service;
public PizzaBakingServicePreparePizza(ITestOutputHelper output)
{
_output = output;
_service = new PizzaBakingService(_logger);
}
public void Dispose()
{
_output.WriteLine(_logger.Dump());
}
[Fact]
public void ReturnsAPizza()
{
var pizza = _service.Prepare();
Assert.NotNull(pizza);
}
[Fact]
public void SetsCrustTypeToThin()
{
var pizza = _service.Prepare();
Assert.Equal("Thin", pizza.CrustType);
}
[Fact]
public void SetsToppingsToPepSaus()
{
var pizza = _service.Prepare();
Assert.Equal(2, pizza.Toppings.Count);
Assert.Contains(pizza.Toppings, t => t == "Pepperoni");
Assert.Contains(pizza.Toppings, t => t == "Sausage");
}
[Fact]
public void SetsBakedToTrue()
{
var pizza = _service.Prepare();
Assert.True(pizza.WasBaked);
}
[Fact]
public void SetsNumSlicesTo8()
{
var pizza = _service.Prepare();
Assert.Equal(8, pizza.NumSlices);
}
}
}
And the tests for the ColdVeggiePizza:
using Xunit;
using Xunit.Abstractions;
namespace DesignPatternsInCSharp.TemplateMethod
{
public class ColdVeggiePizzaBakingServicePrepare
{
private readonly ITestOutputHelper _output;
public ColdVeggiePizzaBakingServicePrepare(ITestOutputHelper output)
{
_output = output;
}
[Fact]
public void ReturnsAPizza()
{
var logger = new LoggerAdapter();
var service = new ColdVeggiePizzaBakingService(logger);
var pizza = service.Prepare();
Assert.NotNull(pizza);
_output.WriteLine(logger.Dump());
}
}
}
11. Template Method applied to simple inheritance
The Template Method can also solve a subtle but important problem with basic inheritance.
11.1 The problem with basic inheritance
namespace DesignPatternsInCSharp.TemplateMethod.Inheritance
{
public abstract class Base
{
private bool _importantSetting;
public virtual void Do()
{
Initialize();
}
private void Initialize()
{
_importantSetting = true;
}
}
public class Child : Base
{
public override void Do()
{
// do other stuff
// PROBLÈME : Initialize() n'est JAMAIS appelée !
}
}
}
The problem: The Child class overrides Do() but does not call base.Do(), which means that Initialize() is never called. The _importantSetting property remains false. There is no way for the compiler or runtime to enforce that base.Do() be called.
Consequences:
- Object may be in an invalid state
- The bug may be difficult to detect
- Documentation alone is not enough to enforce the call to the base method
11.2 The solution with Template Method
namespace DesignPatternsInCSharp.TemplateMethod.Inheritance
{
public abstract class TemplateBase
{
private bool _importantSetting;
// *** LE TEMPLATE METHOD — NON virtual ***
public void Do()
{
BeforeDoing();
Initialize(); // Toujours appelée — garanti par le template method
AfterDone();
}
public virtual void BeforeDoing()
{ }
public abstract void AfterDone();
private void Initialize()
{
_importantSetting = true;
}
}
public class TemplateChild : TemplateBase
{
public override void AfterDone()
{
// do other stuff
// Initialize() est TOUJOURS appelée avant ceci — garanti !
}
}
}
The solution: The Do() method is now non-virtual. Child classes cannot override it. Initialize() is always called, regardless of the implementation of AfterDone(). Child classes provide their custom behaviors via BeforeDoing() (optional hook) and AfterDone() (required abstract method).
Comparison of the two approaches
| Criterion | Simple inheritance | Template Method |
|---|---|---|
Initialize() always called | ❌ Not guaranteed | ✅ Guaranteed |
| Extensibility | ✅ Total | ✅ Controlled |
| Design Safety | ❌ Depends on developer | ✅ Imposed by code |
| Risk of error | High | Low |
| Document usage | Necessary | Not necessary |
12. Related Design Patterns
12.1 Factory Method
The Factory Method is often called by the Template Method. Frequently, the result of the template method is a returned object, and part of the process of producing that object may be to invoke an appropriate Factory Method, perhaps in addition to other steps.
In our example, the _item = new T() line in the Prepare() method is an implicit form of Factory Method — it creates an instance of the appropriate type via the generic parameter.
12.2 Strategy Pattern
The Strategy Pattern is another behavioral pattern very closely related to the Template Method.
| Criterion | Template Method | Strategy |
|---|---|---|
| Mechanism | Legacy | Composition (delegation) |
| Granularity | Vary the steps of an algorithm | Varies the entire algorithm |
| Flexibility | Determined at compilation | Can be changed at runtime |
| Hollywood Principle | ✅ | ✅ |
| Open/Closed principle | ✅ | ✅ |
The Strategy Pattern provides a way to vary an entire algorithm used by one class by delegating it to another class via composition. The Template Method encapsulates an algorithm but allows it to vary among its child classes via inheritance.
When to choose one or the other:
- Use Template Method when variations are limited and known in advance, and you want to enforce structure via inheritance
- Use Strategy when you need more flexibility to change algorithms at runtime, or when you want to avoid inheritance
12.3 Rules Engine Pattern
The Rules Engine Pattern frequently exploits the Template Method in its implementation.
- Processing of individual rules can follow a process defined in a base rule class
- Likewise, the engine itself can inherit behavior from a base rule engine class that defines how it should operate
13. Applied design principles
13.1 Open/Closed Principle (OCP)
“The code should be open for extension while being closed for modification.”
The Template Method perfectly respects this SOLID principle:
- Open to extension: We can add new products (eg:
CalzoneBakingService) without modifying the base class - Closed for modification: The template method
Prepare()does not change when adding new types
13.2 Hollywood Principle
“Don’t call us; we’ll call you.”
This principle is embodied in the Template Method:
- Child classes do not call template method
- They provide implementations that the template method will call at the right time
- flow control remains in base class
14. Summary and key points
What we learned
The Template Method Design Pattern for C# developers can be summarized as follows:
| Item | Description |
|---|---|
| Category | Behavioral pattern |
| Definition | Method in a base class that provides the skeleton of an operation, allowing children to implement the detailed steps |
| Mechanism | Legacy |
| Template method | Non-virtual, defines the algorithm |
| Steps | Abstract or virtual, implemented by subclasses |
| Hooks | Empty body virtual methods, optional |
Common uses of the pattern
- Follow a recipe or step-by-step process
- Page or control lifecycle (like ASP.NET Web Forms)
- Game management and determination of the winner
- Process Extract Transform Load (ETL)
- Document Processing Frameworks
- Improve simple inheritance to ensure basic logic is executed
Supported Design Principles
- Open/Closed Principle (one of the SOLID principles): our code must be open to extension while being closed to modification
- Hollywood Principle: “Don’t call us; we’ll call you”
Related patterns
| Pattern | Relationship |
|---|---|
| Factory Method | Often called by the Template Method to create objects |
| Strategy Pattern | Alternative based on composition rather than inheritance |
| Rules Engine Pattern | Frequently exploits the Template Method in its implementation |
When to use the Template Method
✅ Use it when:
- You have multiple classes with similar algorithms but varying steps
- You want to impose an algorithm structure that subclasses cannot bypass
- You are building a framework with controlled extension points
- You want to ensure that an initialization is always performed
❌ Avoid it when:
- You need to change algorithm at runtime (prefer Strategy)
- Inheritance hierarchy becomes too deep or complex
- Variations are too numerous and unstructured
15. Pattern structure diagram
┌──────────────────────────────────────────────────────┐
│ AbstractClass (PanFoodServiceBase<T>) │
│ │
│ + Prepare() [non-virtual — LE TEMPLATE METHOD] │
│ ├─ new T() │
│ ├─ PrepareCrust() ←── abstract │
│ ├─ AddToppings() ←── abstract │
│ ├─ Cover() ←── virtual (hook, vide) │
│ ├─ if (RequiresBaking) → Bake() ←── virtual │
│ └─ Slice() ←── abstract │
│ │
│ # PrepareCrust() : abstract │
│ # AddToppings() : abstract │
│ # Cover() : virtual (vide) │
│ # Bake() : virtual (implémentation défaut) │
│ # Slice() : abstract │
└──────────────────────────────────────────────────────┘
△ △ △
│ │ │
│ │ │
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────────────┐
│ PieBakingService│ │PizzaBakingService│ │ColdVeggiePizzaBaking │
│ │ │ │ │Service │
│ PrepareCrust() │ │ PrepareCrust() │ │ PrepareCrust() │
│ AddToppings() │ │ AddToppings() │ │ AddToppings() │
│ Cover() ✓ │ │ (no Cover) │ │ (no Cover) │
│ Bake() ✓ │ │ Bake() ✓ │ │ (no Bake — RequiresBak- │
│ Slice() │ │ Slice() │ │ ing = false) │
└─────────────────┘ └──────────────────┘ │ Slice() │
└─────────────────────────┘
Execution flow for each product
Client appelle: service.Prepare()
│
▼
[TEMPLATE METHOD Prepare()]
│
├─→ PrepareCrust() [implémentation de la sous-classe]
│
├─→ AddToppings() [implémentation de la sous-classe]
│
├─→ Cover() [hook : sous-classe l'implémente OU vide par défaut]
│
├─→ if (RequiresBaking)
│ └─→ Bake() [implémentation de la sous-classe OU défaut de base]
│
├─→ Slice() [implémentation de la sous-classe]
│
└─→ return _item
Source code is available in the Design Patterns in C# GitHub repository (github.com/ardalis/DesignPatternsInCSharp).*
Search Terms
c-sharp · design · patterns · template · method · testing · architecture · c# · .net · development · pattern · class · abstract · behavior · inheritance · principle · application · applied · approaches · baking · base · concrete · document · extract