Think about how a recipe works. Making tea and making coffee follow the same skeleton:
- Boil water
- Add the main ingredient (tea leaves / coffee powder)
- Add extras (milk, sugar)
- Pour into cup
The steps and their order never change. Only one or two steps differ between tea and coffee. You don't rewrite the whole procedure for each drink — you keep the skeleton and swap the parts that vary.
That's the Template Method pattern: a parent class defines the fixed skeleton of an algorithm, and subclasses fill in specific steps without changing the overall order.
The problem it solves
Say you process two report types — PDF and Excel. Both follow the same flow: fetch data, format it, export, then email it. Without Template Method, you copy-paste that flow twice and each copy drifts out of sync over time:
public void GeneratePdfReport()
{
var data = FetchData(); // same
// ...format as PDF... // different
// ...export... // different
SendEmail(); // same
}
public void GenerateExcelReport()
{
var data = FetchData(); // same — duplicated
// ...format as Excel... // different
// ...export... // different
SendEmail(); // same — duplicated
}
The shared steps are duplicated, and if the flow changes, you have to fix it in every copy.
The Template Method way
The parent class owns the skeleton in one place. The steps that vary become abstract methods for subclasses to implement:
public abstract class ReportGenerator
{
// The template method — defines the fixed skeleton. Note: it's NOT overridable.
public void Generate()
{
var data = FetchData(); // shared step
Format(data); // varies — subclass decides
Export(); // varies — subclass decides
SendEmail(); // shared step
}
private string FetchData() { Console.WriteLine("Fetching data"); return "data"; }
private void SendEmail() => Console.WriteLine("Emailing report");
// The blanks — subclasses must fill these in
protected abstract void Format(string data);
protected abstract void Export();
}
Each subclass fills only the blanks:
public class PdfReport : ReportGenerator
{
protected override void Format(string data) => Console.WriteLine("Formatting as PDF");
protected override void Export() => Console.WriteLine("Exporting PDF");
}
public class ExcelReport : ReportGenerator
{
protected override void Format(string data) => Console.WriteLine("Formatting as Excel");
protected override void Export() => Console.WriteLine("Exporting Excel");
}
Run it:
ReportGenerator report = new PdfReport();
report.Generate();
// Fetching data
// Formatting as PDF
// Exporting PDF
// Emailing report
The Generate() flow lives in exactly one place. Subclasses can't reorder or skip steps — they only fill the blanks. Add a CSV report? Write one subclass with two methods; the skeleton is untouched.
The key idea: "don't call us, we'll call you"
Notice the control flow. You don't call Format() and Export() yourself — the parent's Generate() calls them for you, at the right moment. The subclass just provides the pieces; the parent drives the sequence. This inversion is sometimes called the Hollywood Principle: "don't call us, we'll call you."
Template Method vs Strategy
They solve a similar problem — varying part of a behavior — but differently:
- Template Method uses inheritance. The skeleton is fixed in a base class; subclasses override steps. The variation is chosen at compile time by which subclass you instantiate.
- Strategy uses composition. You inject a whole algorithm object and can swap it at runtime.
Rule of thumb: fixed skeleton with a few pluggable steps → Template Method. Whole interchangeable algorithm, swappable on the fly → Strategy.
Where you've seen it
-
ASP.NET Core middleware and framework base classes — you override specific methods (
OnActionExecuting,ConfigureServices) while the framework runs the overall pipeline. -
Unit test lifecycles —
Setup(), the test body,Teardown()run in a fixed order; you fill in the parts. - Data import pipelines — validate, transform, save always run in that order; each importer customizes transform.
One line to remember
Template Method = a recipe with blanks. The parent locks the steps and their order; subclasses fill in only the parts that differ.
Part 14 of a series on must-know design patterns in C#, explained the simple way. Earlier parts covered Singleton through State. Next up: Chain of Responsibility — passing a request down a line of handlers.
Top comments (0)