Template Method Pattern in C# Explained
This article provides an overview of the Template Method design pattern and details how abstract base classes in C# are used to construct skeleton algorithms. You will learn the core architecture of the pattern, the distinction between abstract steps and optional hooks, and how to implement a clean, reusable workflow in C# that allows subclasses to alter specific behaviors without changing the overarching algorithm structure.
What is the Template Method Pattern?
The Template Method is a behavioral design pattern that defines the programmatic skeleton of an algorithm in a base class, deferring some specific steps to derived subclasses. It allows subclasses to redefine certain steps of an algorithm without changing the overall algorithm's sequence or invariant structure.
This pattern embodies the "Hollywood Principle": Don't call us, we'll call you. Instead of derived classes invoking methods from the parent at will, the base class controls the execution flow and invokes subclass implementations when needed.
Defining Skeleton Algorithms with Abstract Base Classes
In C#, the Template Method pattern relies on abstract
base classes to enforce structural rules while granting implementation
flexibility:
- The Template Method: A public, non-virtual (or sealed) method in the abstract base class that outlines the algorithm's execution sequence. Marking this method non-virtual prevents subclasses from altering the execution order.
- Abstract Operations: Methods declared with the
abstractkeyword that have no implementation in the base class. Concrete subclasses must implement these methods to supply required behavior. - Hook Operations: Methods declared with the
virtualkeyword that provide default, empty, or optional behavior. Subclasses can optionally override hooks to extend functionality at specific points in the algorithm.
Implementing the Template Method in C
The following example demonstrates a data processing workflow where the overarching steps—opening a file, parsing data, and closing the file—are orchestrated by the abstract base class, while specific file formats handle their own parsing logic.
using System;
// Abstract Base Class
public abstract class DataProcessor
{
// The Template Method: defines the skeleton of the algorithm
public void Process(string filePath)
{
OpenFile(filePath);
ReadRawData();
ParseData(); // Subclass-specific step
if (ShouldGenerateAuditLog()) // Hook method
{
LogAudit();
}
CloseFile();
}
private void OpenFile(string filePath)
{
Console.WriteLine($"Opening file: {filePath}");
}
private void ReadRawData()
{
Console.WriteLine("Reading raw data into memory.");
}
private void CloseFile()
{
Console.WriteLine("Closing file handles and releasing resources.");
}
// Abstract method: subclasses MUST implement this
protected abstract void ParseData();
// Hook method: subclasses CAN override this, but have a default
protected virtual bool ShouldGenerateAuditLog()
{
return false;
}
protected virtual void LogAudit()
{
Console.WriteLine("Audit logged: Processing complete.");
}
}
// Concrete Subclass 1: CSV Processor
public class CsvDataProcessor : DataProcessor
{
protected override void ParseData()
{
Console.WriteLine("Parsing comma-separated values into objects.");
}
}
// Concrete Subclass 2: JSON Processor with Hook
public class JsonDataProcessor : DataProcessor
{
protected override void ParseData()
{
Console.WriteLine("Deserializing JSON payload into dynamic schema.");
}
protected override bool ShouldGenerateAuditLog()
{
return true; // Overriding hook to enable audit logging
}
}
// Execution
public class Program
{
public static void Main()
{
DataProcessor csvProcessor = new CsvDataProcessor();
csvProcessor.Process("records.csv");
Console.WriteLine();
DataProcessor jsonProcessor = new JsonDataProcessor();
jsonProcessor.Process("payload.json");
}
}Key Benefits and Considerations
- Eliminates Code Duplication: Common execution logic (opening files, resource cleanup, control flow) resides solely in the base class, adhering to the Don't Repeat Yourself (DRY) principle.
- Controlled Customization: By controlling which
methods are
abstract,virtual, or non-virtual, you dictate precisely where external code is allowed to intervene. - Inheritance Constraints: Because C# does not support multiple class inheritance, using the Template Method limits your derived classes to a single inheritance chain. If only algorithm composition is required without shared lifecycle logic, behavioral patterns like the Strategy Pattern may serve as an alternative.