Template Method Pattern
Definitionβ
The Template Method defines the skeleton of an algorithm in a method, deferring some steps to subclasses. It lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.
Coffee Shop Exampleβ
Every hot beverage follows the same preparation algorithm: grind beans β brew β pour in cup β add condiments. But each beverage type customizes specific steps β espresso uses fine grind and no condiments, while latte uses medium grind and adds steamed milk.
Structureβ
Abstract Class β The Algorithm Skeletonβ
public abstract class BeverageMaker
{
// Template Method β defines the algorithm skeleton
// Marked sealed so subclasses can't override the overall structure
public void Prepare()
{
GrindBeans();
Brew();
PourInCup();
if (CustomerWantsCondiments())
AddCondiments();
Console.WriteLine();
}
// Abstract steps β MUST be overridden
protected abstract void GrindBeans();
protected abstract void Brew();
protected abstract void AddCondiments();
// Concrete step β shared by all beverages
protected virtual void PourInCup() =>
Console.WriteLine(" Pouring into cup...");
// Hook β optional override (default behavior provided)
protected virtual bool CustomerWantsCondiments() => true;
}
Concrete Classes β Customizing Stepsβ
public class EspressoMaker : BeverageMaker
{
protected override void GrindBeans() =>
Console.WriteLine(" Grinding 18g fine espresso beans");
protected override void Brew() =>
Console.WriteLine(" Extracting at 9 bar pressure for 25 seconds");
protected override void AddCondiments() =>
Console.WriteLine(" Serving straight β no condiments for espresso");
// Hook override β espresso purists don't want extras
protected override bool CustomerWantsCondiments() => false;
}
public class LatteMaker : BeverageMaker
{
protected override void GrindBeans() =>
Console.WriteLine(" Grinding 18g medium espresso beans");
protected override void Brew() =>
Console.WriteLine(" Pulling double espresso shot");
protected override void AddCondiments() =>
Console.WriteLine(" Adding steamed milk (silky microfoam)");
}
public class CappuccinoMaker : BeverageMaker
{
protected override void GrindBeans() =>
Console.WriteLine(" Grinding 18g fine espresso beans");
protected override void Brew() =>
Console.WriteLine(" Pulling espresso shot");
protected override void AddCondiments() =>
Console.WriteLine(" Adding steamed milk + thick foam layer");
}
Clientβ
var makers = new BeverageMaker[]
{
new EspressoMaker(),
new LatteMaker(),
new CappuccinoMaker(),
};
foreach (var maker in makers)
{
Console.WriteLine($"Preparing {maker.GetType().Name.Replace("Maker", "")}:");
maker.Prepare();
}
// Output:
// Preparing Espresso:
// Grinding 18g fine espresso beans
// Extracting at 9 bar pressure for 25 seconds
// Pouring into cup...
//
// Preparing Latte:
// Grinding 18g medium espresso beans
// Pulling double espresso shot
// Pouring into cup...
// Adding steamed milk (silky microfoam)
//
// Preparing Cappuccino:
// Grinding 18g fine espresso beans
// Pulling espresso shot
// Pouring into cup...
// Adding steamed milk + thick foam layer
Notice the EspressoMaker overrides the hook CustomerWantsCondiments() to return false. The algorithm skips AddCondiments() entirely. This is a hook β a method with a default behavior that subclasses can optionally override.
Types of Methods in Template Methodβ
| Type | Purpose | Modifier |
|---|---|---|
| Template Method | Defines the algorithm skeleton | public sealed |
| Abstract methods | Must be overridden by subclasses | protected abstract |
| Concrete methods | Shared behavior (default implementation) | protected virtual |
| Hooks | Optional overrides with default behavior | protected virtual returning bool |
The Hollywood Principleβ
"Don't call us, we'll call you."
The base class calls the subclass methods, not the other way around. The template method controls the flow β subclasses provide implementation details. This is the Hollywood Principle in action.
Template Method vs Strategyβ
| Template Method | Strategy | |
|---|---|---|
| Mechanism | Inheritance | Composition |
| When to choose | Algorithm structure is fixed, steps vary | Entire algorithm varies |
| Extensibility | Subclass per variation | New strategy class |
| Coupling | Tighter (inheritance) | Looser (composition) |
| Runtime change | No (fixed at compile time) | Yes (inject different strategy) |
.NET Real-World Usageβ
StreamβRead()/Write()are template methods; subclasses overrideReadByte()/WriteByte()- ASP.NET Core
ControllerBaseβ lifecycle methods likeOnActionExecuting() IHostedServiceβStartAsync/StopAsyncas template for background servicesBackgroundServiceβExecuteAsync()is a template method with the skeleton provided- Test fixtures β
SetUp()/TearDown()in unit testing frameworks
When to Useβ
- You have a fixed algorithm structure with variable steps
- Common behavior across subclasses should be in one place
- You want to control the extension points (Hollywood Principle)
When NOT to Useβ
- The algorithm varies entirely β use Strategy instead
- There are few common steps β inheritance overhead isn't worth it
- You need to change behavior at runtime β Strategy is better
Key Takeawaysβ
- Template Method defines the algorithm skeleton in a base class
- Subclasses override specific steps without changing the overall structure
- Hooks provide optional customization points with default behavior
- Uses inheritance β if composition is preferred, use Strategy
Interview Questionsβ
Q: Why mark the template method as sealed?
To prevent subclasses from changing the algorithm's structure. The whole point is that the base class controls the flow. If subclasses can override the template method itself, the guarantee is lost.
Q: How is Template Method different from polymorphism? Template Method is a specific use of polymorphism. Regular polymorphism means a subclass overrides a method. Template Method adds a fixed calling order β the base class calls multiple abstract/virtual methods in a specific sequence. The structure is the key innovation.
Q: What's a "hook" in Template Method?
A hook is a method with a default implementation that subclasses can optionally override. It's not abstract β it provides a sensible default. Example: CustomerWantsCondiments() returns true by default, but EspressoMaker overrides it to return false.
Related Topicsβ
- Strategy β composition-based alternative
- Builder β similar step-by-step, but for construction
- Factory Method β another pattern using inheritance hooks
- SOLID β OCP & Hollywood Principle β extend behavior without changing structure