Template Method Pattern
Overview
Every report trims input, rejects an empty value and formats the result. Copying the sequence into each format makes it easier to omit a check or change the order accidentally.
Template Method keeps that algorithm skeleton in one method and delegates selected steps to subclasses. Template refers to the execution structure here, not a document file.
UML
ReportTemplate.Run() normalizes and validates input before calling Format(). UpperReport and JsonReport implement Format() without overriding Run().
History
GoF cataloged Template Method as a behavioral pattern in Design Patterns in 1994. Subclasses vary selected steps while preserving the algorithm structure.1
Why use it?
The required sequence is visible in one place, while each implementation handles formatting. Run() is non-virtual, so subclasses cannot override this path to reorder its steps.
Inheritance fixes the step implementation to the object type. Strategy instead supplies a rule through an object or function, which offers a different way to select or replace behavior.
Example
Trim the input, then format an uppercase report.
using System;
using System.Collections.Generic;
using System.Linq;
ExportResult result = new UpperReport().Run(" hello ");
Console.WriteLine(result.Success + ": " + result.Text);
sealed record ExportResult(bool Success, string Text);
abstract class ReportTemplate
{
public ExportResult Run(string input)
{
string value = input.Trim();
if (value.Length == 0) return new ExportResult(false, "EMPTY_INPUT");
return new ExportResult(true, "REPORT|" + Format(value));
}
protected abstract string Format(string value);
}
sealed class UpperReport : ReportTemplate
{
protected override string Format(string value) => value.ToUpperInvariant();
}
sealed class JsonReport : ReportTemplate
{
protected override string Format(string value) => System.Text.Json.JsonSerializer.Serialize(value);
}
True: REPORT|HELLORun() owns normalization, validation and formatting order. UpperReport supplies only the uppercase step.
An extended example
Select JSON formatting, then try empty input. The empty value returns a failure before Format() is called.
using System;
using System.Collections.Generic;
using System.Linq;
ReportTemplate report = new JsonReport();
foreach (string input in new[] { " hello ", " " })
{
ExportResult result = report.Run(input);
Console.WriteLine(result.Success + ": " + result.Text);
}
sealed record ExportResult(bool Success, string Text);
abstract class ReportTemplate
{
public ExportResult Run(string input)
{
string value = input.Trim();
if (value.Length == 0) return new ExportResult(false, "EMPTY_INPUT");
return new ExportResult(true, "REPORT|" + Format(value));
}
protected abstract string Format(string value);
}
sealed class UpperReport : ReportTemplate
{
protected override string Format(string value) => value.ToUpperInvariant();
}
sealed class JsonReport : ReportTemplate
{
protected override string Format(string value) => System.Text.Json.JsonSerializer.Serialize(value);
}
True: REPORT|"hello"
False: EMPTY_INPUTThe result separates success from its text so invalid input is not treated as a normal report. Avoid creating an inheritance hierarchy for a single function when there is no useful shared workflow.
Related reading
Each example is a separate .NET 10 console program.
Footnotes
- Gamma, Helm, Johnson, Vlissides. Design Patterns (1994). Publisher record. Behavioral patterns, Template Method. ↩