BrandGhost
When to Use Template Method Pattern in C#: Decision Guide with Examples

When to Use Template Method Pattern in C#: Decision Guide with Examples

When to Use Template Method Pattern in C#: Decision Guide with Examples

Knowing how a design pattern works is only half the story. The harder question is knowing when to reach for it. When to use the template method pattern in C# is a decision that hinges on whether your classes share an algorithm's shape but differ in specific steps, and whether you want the base class to enforce the order those steps execute. Get this decision right and your code stays consistent, extensible, and easy to maintain.

This article walks you through the decision criteria, two practical C# scenarios, the situations where the pattern does more harm than good, and a head-to-head comparison with the strategy pattern.

Decision Criteria for the Template Method Pattern

Before writing any code, you need a clear picture of when the template method pattern earns its place in your design. The pattern is built around one central idea: a base class defines the skeleton of an algorithm and lets subclasses fill in specific steps without changing the overall structure.

Multiple Classes Share the Same Algorithm Structure

The strongest signal for the template method pattern is when you look at two or more classes and notice they follow the same sequence of high-level steps but implement certain steps differently. Maybe both classes validate input, then transform data, then persist results -- but the transformation and persistence logic varies. If you extracted the shared structure into a base class and left the varying parts as abstract methods, you'd have the template method pattern.

The key word is "structure." The overall flow must be the same. If one class validates then transforms then persists, but another transforms first then validates -- that's not a shared algorithm structure. That's two different algorithms, and the template method pattern will force a false uniformity.

You Want to Enforce a Particular Execution Order

One of the template method pattern's defining strengths is that the base class controls the sequence. Subclasses can't skip steps, reorder them, or inject new ones between existing steps (unless you explicitly provide hooks). This is a feature, not a limitation -- when you genuinely need that guarantee.

Think about scenarios where step order matters for correctness. Validation must happen before processing. Logging must happen after completion. Resource cleanup must run regardless of success or failure. When step order is a correctness requirement, the template method pattern gives you a single place to enforce it.

Common Pre/Post Processing Logic Should Live in One Place

If every concrete implementation needs the same setup before the "real work" and the same teardown afterward, duplicating that logic across classes is a maintenance hazard. The template method pattern lets you centralize pre-processing and post-processing in the base class while delegating the unique work to subclasses. Changes to the shared logic happen in one place and propagate automatically.

This is closely related to the inversion of control principle -- the base class controls the flow and calls down into subclass-specific behavior rather than the other way around.

You Need Hooks for Optional Customization

Not every step in your algorithm is mandatory for subclasses to override. The template method pattern supports hook methods -- virtual methods with a default (often empty) implementation that subclasses can optionally override. This gives you extensibility points without forcing every subclass to provide an implementation.

Hooks are perfect for optional notifications, logging, or conditional behavior. A subclass that needs custom notification logic overrides the hook. A subclass that doesn't need it inherits the default no-op and moves on. This keeps the inheritance hierarchy lean by avoiding abstract methods for truly optional behavior.

Scenario: Data Processing Pipelines

ETL-style (Extract, Transform, Load) pipelines are a natural fit for the template method pattern because the overall process is consistent -- validate incoming data, transform it into the target format, load it into the destination, and notify stakeholders -- but the specifics of transformation and loading vary by data source.

Here's a C# implementation that demonstrates when to use the template method pattern in C# for a data processing pipeline:

using System;
using System.Collections.Generic;

namespace DataProcessing;

public abstract class DataProcessor
{
    // The template method -- defines the algorithm skeleton
    public void Process(string source)
    {
        Console.WriteLine($"Starting processing: {source}");

        var rawData = Extract(source);
        Validate(rawData);
        var transformed = Transform(rawData);
        Load(transformed);
        OnProcessingComplete(source);

        Console.WriteLine($"Finished processing: {source}");
    }

    private List<string> Extract(string source)
    {
        Console.WriteLine($"  Extracting data from: {source}");
        return new List<string> { "row1", "row2", "row3" };
    }

    private void Validate(List<string> data)
    {
        if (data == null || data.Count == 0)
        {
            throw new InvalidOperationException(
                "No data to process.");
        }

        Console.WriteLine($"  Validated {data.Count} records.");
    }

    // Subclasses must implement transformation logic
    protected abstract List<string> Transform(List<string> rawData);

    // Subclasses must implement loading logic
    protected abstract void Load(List<string> transformedData);

    // Hook -- optional notification after processing
    protected virtual void OnProcessingComplete(string source)
    {
        // Default: no notification
    }
}

The Process method is the template method. It locks down the execution order: extract, validate, transform, load, notify. No subclass can skip validation or load before transforming. The two abstract methods -- Transform and Load -- are the variation points. The OnProcessingComplete hook lets subclasses optionally add notification behavior.

Now the concrete implementations:

namespace DataProcessing;

public sealed class CsvDataProcessor : DataProcessor
{
    protected override List<string> Transform(List<string> rawData)
    {
        Console.WriteLine("  Transforming CSV data...");
        var result = new List<string>();
        foreach (var row in rawData)
        {
            // CSV-specific parsing and normalization
            result.Add($"csv_parsed:{row.Trim().ToUpperInvariant()}");
        }

        return result;
    }

    protected override void Load(List<string> transformedData)
    {
        Console.WriteLine(
            $"  Loading {transformedData.Count} CSV records " +
            "into relational database...");
    }

    protected override void OnProcessingComplete(string source)
    {
        Console.WriteLine(
            $"  Sending CSV import report for: {source}");
    }
}

public sealed class JsonDataProcessor : DataProcessor
{
    protected override List<string> Transform(List<string> rawData)
    {
        Console.WriteLine("  Transforming JSON data...");
        var result = new List<string>();
        foreach (var row in rawData)
        {
            // JSON-specific deserialization and mapping
            result.Add($"json_mapped:{row}");
        }

        return result;
    }

    protected override void Load(List<string> transformedData)
    {
        Console.WriteLine(
            $"  Loading {transformedData.Count} JSON documents " +
            "into document store...");
    }
}

Notice CsvDataProcessor overrides the OnProcessingComplete hook to send reports, while JsonDataProcessor skips it entirely and inherits the default empty implementation. Both classes follow the exact same algorithm structure, but their transformation and loading logic is completely different. The template method pattern in C# ensures the pipeline stays consistent across data formats without duplicating the orchestration logic.

Scenario: Report Generation

Report generation is another domain where the template method pattern shines. Every report follows the same high-level workflow: gather the underlying data, format the header, format the body, format the footer, and export the result. But the formatting and export mechanics vary based on the output format.

using System;
using System.Collections.Generic;

namespace ReportGeneration;

public abstract class ReportGenerator
{
    public void GenerateReport(string reportName)
    {
        Console.WriteLine($"Generating report: {reportName}");

        var data = GatherData(reportName);
        var header = FormatHeader(reportName);
        var body = FormatBody(data);
        var footer = FormatFooter();
        ApplyWatermark(header);
        Export(header, body, footer, reportName);

        Console.WriteLine($"Report complete: {reportName}");
    }

    private Dictionary<string, object> GatherData(string reportName)
    {
        Console.WriteLine("  Gathering report data...");
        return new Dictionary<string, object>
        {
            ["title"] = reportName,
            ["generatedAt"] = DateTime.UtcNow,
            ["rows"] = 150
        };
    }

    protected abstract string FormatHeader(string reportName);

    protected abstract string FormatBody(
        Dictionary<string, object> data);

    protected abstract string FormatFooter();

    // Hook for optional watermark -- not every format
    // needs one
    protected virtual void ApplyWatermark(string header)
    {
        // Default: no watermark applied
    }

    protected abstract void Export(
        string header,
        string body,
        string footer,
        string reportName);
}

The GenerateReport method enforces the step sequence. ApplyWatermark is a hook -- useful for PDF reports that need a "DRAFT" or "CONFIDENTIAL" stamp, but irrelevant for spreadsheet exports. Here are two concrete generators:

namespace ReportGeneration;

public sealed class PdfReportGenerator : ReportGenerator
{
    protected override string FormatHeader(string reportName)
    {
        return $"[PDF HEADER] {reportName} " +
               $"-- Generated {DateTime.UtcNow:yyyy-MM-dd}";
    }

    protected override string FormatBody(
        Dictionary<string, object> data)
    {
        return $"[PDF BODY] {data["rows"]} rows rendered " +
               "as styled tables with charts";
    }

    protected override string FormatFooter()
    {
        return "[PDF FOOTER] Page numbers and legal disclaimer";
    }

    protected override void ApplyWatermark(string header)
    {
        Console.WriteLine("  Applying CONFIDENTIAL watermark " +
            "to PDF header...");
    }

    protected override void Export(
        string header,
        string body,
        string footer,
        string reportName)
    {
        Console.WriteLine(
            $"  Exporting {reportName}.pdf with " +
            "embedded fonts and vector graphics...");
    }
}

public sealed class ExcelReportGenerator : ReportGenerator
{
    protected override string FormatHeader(string reportName)
    {
        return $"[EXCEL HEADER] {reportName} | Sheet1";
    }

    protected override string FormatBody(
        Dictionary<string, object> data)
    {
        return $"[EXCEL BODY] {data["rows"]} rows mapped " +
               "to worksheet cells with formulas";
    }

    protected override string FormatFooter()
    {
        return "[EXCEL FOOTER] Summary row with totals";
    }

    protected override void Export(
        string header,
        string body,
        string footer,
        string reportName)
    {
        Console.WriteLine(
            $"  Exporting {reportName}.xlsx with " +
            "auto-sized columns and filters...");
    }
}

PdfReportGenerator overrides ApplyWatermark to stamp documents. ExcelReportGenerator doesn't -- spreadsheets don't need watermarks, so it inherits the no-op default. Both generators produce reports with identical step ordering, and the base class guarantees that data gathering always precedes formatting. This is a classic case where the template method pattern in C# prevents subtle bugs that would arise if individual generators implemented the full workflow independently.

When NOT to Use Template Method

Knowing when the template method pattern fits is only valuable if you also recognize when it doesn't. Misapplying this pattern creates the kind of inheritance tangles that give object-oriented programming a bad reputation.

The Algorithm Structure Varies Entirely

If your classes don't actually share a common step sequence, the template method pattern forces an artificial uniformity. You end up with a base class that has so many conditional branches or empty "optional" steps that it obscures rather than clarifies the algorithm. When two classes need fundamentally different workflows, they should be separate classes -- not siblings in an inheritance hierarchy.

In these cases, the strategy pattern is often the better choice because it lets each implementation define its own algorithm completely, without inheriting a fixed skeleton.

You Need Runtime Algorithm Swapping

The template method pattern uses inheritance, which means the algorithm variant is chosen at object creation time. You can't switch a CsvDataProcessor to behave like a JsonDataProcessor after instantiation. If your application needs to swap processing strategies at runtime -- based on user input, configuration, or dynamic conditions -- the strategy pattern gives you that flexibility through composition rather than inheritance.

The Inheritance Hierarchy Gets Too Deep

Template method relies on inheritance, and inheritance hierarchies have a well-known scaling problem. Two levels deep is usually fine. Three levels can work. Four or more levels and you're dealing with fragile base class issues where a change to any ancestor ripples unpredictably through the tree.

If you're already deep in an inheritance hierarchy, step back and evaluate whether composition-based alternatives -- like dependency injection with IServiceCollection -- would give you the same extensibility without the coupling.

The Base Class Accumulates Too Many Abstract Methods

A template method base class with two or three abstract methods is clean. A base class with eight or ten abstract methods is a code smell. It means the "shared algorithm" is so fragmented that almost every step is a variation point, and the base class provides very little actual structure.

When you notice this, consider decomposing the algorithm. Break the base class into smaller collaborators, each responsible for a focused piece of the workflow. This often leads toward a design that uses the facade pattern to coordinate those collaborators behind a simple interface.

Template Method vs Strategy: Choosing the Right Pattern

Template method and strategy are the two patterns developers most often confuse because both deal with varying behavior across a common concept. The difference is structural: template method uses inheritance to vary steps within a fixed algorithm, while strategy uses composition to vary entire algorithms.

Here's a decision framework for choosing between them:

Is the algorithm skeleton fixed across all variants? If the high-level steps and their order are the same for every variant, and only specific steps change, the template method pattern is the natural fit. If the overall algorithm can vary -- different steps, different order, different number of steps -- the strategy pattern is more appropriate because each strategy defines its own complete algorithm.

Do you need to swap behavior at runtime? Template method binds behavior at object creation through inheritance. Strategy binds behavior through a composable interface that can be injected or swapped without creating a new object. If you need runtime flexibility, strategy wins.

Do you need to enforce step ordering? Template method gives you a single place to enforce the sequence. Strategy delegates the entire algorithm to the implementation, which means each strategy is responsible for its own ordering. If step order is a correctness requirement, template method's centralized control is a significant advantage.

How deep is your inheritance hierarchy? Template method adds a layer of inheritance. If your class already inherits from a domain-specific base, adding a template method base on top creates a deeper hierarchy. Strategy avoids this by relying on interfaces and composition, which is often more flexible in codebases that use dependency injection.

In practice, the two patterns complement each other. You might use a template method to define the fixed skeleton and inject strategy objects for the variable steps -- gaining the ordering guarantees of template method with the flexibility of strategy.

Real-World .NET Framework Examples

The template method pattern isn't an academic exercise -- it's embedded throughout the .NET class library itself. Recognizing these examples helps you internalize when the pattern fits.

System.IO.Stream is one of the most visible examples. The Stream base class defines the algorithm for reading and writing data through public methods like Read, Write, and CopyTo. Concrete implementations like FileStream, MemoryStream, and NetworkStream override protected methods to provide the actual I/O behavior. The base class handles validation, buffering, and lifecycle management -- subclasses only handle the transport-specific work. You can explore the Stream class in the official Microsoft documentation.

HttpMessageHandler follows a similar structure. The HttpClient pipeline calls SendAsync on the handler, and concrete handlers like HttpClientHandler and DelegatingHandler override the method to provide their specific behavior. DelegatingHandler itself is a template method variant -- it defines a pipeline step and delegates to an inner handler, letting you chain cross-cutting concerns like logging, retry logic, and authentication.

System.Collections.ObjectModel.Collection<T> provides protected virtual methods like InsertItem, RemoveItem, and ClearItems that subclasses override to customize behavior while the base class handles indexing and enumeration. ObservableCollection<T> uses this approach to fire change notifications without reimplementing collection mechanics.

Frequently Asked Questions

What is the template method pattern in C#?

The template method pattern is a behavioral design pattern where a base class defines the skeleton of an algorithm in a method, deferring some steps to subclasses. The base class controls the sequence and provides common logic, while subclasses override specific steps to customize behavior. In C#, you implement it with an abstract base class containing a non-virtual template method that calls abstract or virtual methods for the varying steps.

How does the template method pattern differ from the strategy pattern in C#?

The template method pattern uses inheritance to vary specific steps within a fixed algorithm skeleton. The strategy pattern uses composition to vary entire algorithms through interchangeable interface implementations. Template method controls step order from the base class. Strategy delegates the full algorithm to the injected strategy object. Choose template method when the skeleton is fixed and step order matters. Choose strategy when you need runtime swapping or entirely different algorithms.

Can I combine the template method pattern with dependency injection?

Yes, and it's a powerful combination. You can use the template method pattern to define the algorithm structure while injecting dependencies into the concrete subclasses through constructor injection. Register your concrete processors with IServiceCollection, and the DI container resolves them with their required services. This keeps the template method's structural guarantees while leveraging the flexibility of inversion of control.

What are hooks in the template method pattern?

Hooks are virtual methods in the base class that provide a default implementation (often empty) but allow subclasses to override them for optional customization. Unlike abstract methods, which subclasses must override, hooks give subclasses the choice. Common uses include optional logging, notifications, conditional validation, or feature flags. In the report generation example above, ApplyWatermark is a hook -- PDF reports override it, but Excel reports use the default no-op.

When should I avoid the template method pattern?

Avoid the template method pattern when your classes don't share a common algorithm structure, when you need to swap algorithms at runtime, when the inheritance hierarchy is already deep, or when the base class would need too many abstract methods (more than four or five is a warning sign). In these situations, composition-based patterns like strategy, the decorator pattern for adding behavior without inheritance, or coordinating patterns like the command pattern, are usually better alternatives.

Does the template method pattern violate the open/closed principle?

When used correctly, the template method pattern supports the open/closed principle. The base class is closed for modification -- its algorithm skeleton doesn't change. But it's open for extension through the abstract and virtual methods that subclasses override. Problems arise when you need to modify the base class's algorithm to accommodate new requirements, which signals that the algorithm skeleton wasn't as stable as you assumed.

How do I test classes that use the template method pattern?

Test the template method's integration by creating concrete subclasses (or test doubles) and verifying that the full algorithm executes in the correct order with the expected results. Test individual overridden steps in isolation by calling them directly on the concrete class. For the hook methods, verify that the default behavior works when not overridden and that custom hook implementations execute at the right point in the sequence.

Wrapping Up the Template Method Pattern Decision Guide

Deciding when to use the template method pattern in C# boils down to a straightforward evaluation: do your classes share a fixed algorithm structure where only specific steps vary, and does the execution order of those steps matter for correctness? If both answers are yes, the template method pattern gives you a clean, maintainable design that centralizes the shared flow and lets subclasses focus on their unique responsibilities.

The data processing pipeline and report generation examples in this article illustrate the pattern's sweet spot -- consistent orchestration with variable implementation details. But the anti-patterns matter just as much. Deep inheritance hierarchies, too many abstract methods, and runtime algorithm swapping are all signals to reach for composition-based alternatives like the strategy pattern instead.

Start with the decision criteria. Check whether the algorithm skeleton is genuinely shared, whether step order needs enforcement, and whether the number of variation points stays manageable. If those conditions hold, the template method pattern will earn its place in your codebase. If they don't, keep your design simpler and more flexible with composition. The best pattern choice is always the one that matches the actual shape of your problem.

Template Method Design Pattern in C#: Complete Guide with Examples

Master the template method design pattern in C# with practical examples showing inheritance-based algorithm customization and real-world .NET implementations.

How to Implement Template Method Pattern in C#: Step-by-Step Guide

Learn how to implement the template method pattern in C# with step-by-step examples covering abstract classes, hooks, sealed methods, and DI integration.

When to Use Factory Method Pattern in C#: Decision Guide with Examples

When to use Factory Method pattern in C#: decision criteria, code examples, and scenarios to determine if Factory Method is the right choice.

An error has occurred. This application may no longer respond until reloaded. Reload