Mastering Design Patterns in C# and ASP.NET Core
Part 4.10 – Template Method Design Pattern
Series: Design Patterns in C# and ASP.NET Core
Pattern Category: Behavioral Design Pattern
Difficulty: ⭐⭐⭐☆☆ Intermediate
Prerequisites: C#, OOP, Inheritance, Abstract Classes, Interfaces, SOLID Principles, Dependency Injection
Table of Contents
Introduction
What is the Template Method Design Pattern?
Why Do We Need the Template Method Pattern?
The Problem with Duplicate Algorithms
Real-World Analogy
Template Method Pattern Structure
UML Class Diagram
Components of the Pattern
Template Method vs Strategy
Complete C# Console Application
Data Processing Example
Payment Processing Example
Hook Methods
ASP.NET Core Implementation
Template Method with Dependency Injection
Real-World Enterprise Examples
Template Method vs Factory Method
Template Method vs Strategy
Template Method vs State
Advantages
Disadvantages
Best Practices
Common Mistakes
Unit Testing
Interview Questions
When Should You Use Template Method?
When Should You Avoid It?
Key Takeaways
Conclusion
Coming Up Next
1. Introduction
In enterprise applications, we often have several processes that follow the same overall sequence, but individual steps differ.
For example, consider importing data from different sources:
Read Data
↓
Validate Data
↓
Transform Data
↓
Save Data
↓
Send Notification
The overall workflow is the same.
However, the implementation can differ for:
CSV
Excel
JSON
XML
Database
External API
If we implement the complete workflow separately for every data source, we can end up with duplicated code.
The Template Method Design Pattern provides a solution.
It allows a base class to define the skeleton of an algorithm, while derived classes implement or customize specific steps.
2. What is the Template Method Design Pattern?
The Template Method Design Pattern defines the skeleton of an algorithm in a base class and allows subclasses to redefine certain steps without changing the overall algorithm structure.
The important concept is:
The base class controls the overall workflow, while derived classes customize individual steps.
For example:
DataProcessor
|
ProcessData()
|
+------------+------------+
| | |
ReadData() Validate() SaveData()
↑ ↑ ↑
| | |
CSV CSV CSV
JSON JSON JSON
XML XML XML
The sequence remains controlled by the base class.
3. Why Do We Need the Template Method Pattern?
Suppose we have three types of data processors:
CSVProcessor
JSONProcessor
XMLProcessor
All of them perform:
Read
Validate
Transform
Save
Without Template Method, we might write:
public void ProcessCsv()
{
ReadCsv();
ValidateCsv();
TransformCsv();
SaveCsv();
}
Then:
public void ProcessJson()
{
ReadJson();
ValidateJson();
TransformJson();
SaveJson();
}
And:
public void ProcessXml()
{
ReadXml();
ValidateXml();
TransformXml();
SaveXml();
}
The sequence is duplicated.
This creates problems.
Problem 1 – Code Duplication
The workflow is repeated.
Problem 2 – Inconsistent Processing
One implementation might accidentally change the order.
For example:
CSV:
Read → Validate → Transform → Save
JSON:
Read → Transform → Validate → Save
Problem 3 – Difficult Maintenance
Changing the workflow requires modifying multiple classes.
Problem 4 – Business Rules Can Drift
Different implementations may gradually behave differently.
The Template Method Pattern centralizes the algorithm.
4. The Problem with Duplicate Algorithms
Imagine a payment workflow:
1. Validate payment
2. Authenticate customer
3. Process payment
4. Save transaction
5. Send notification
Credit Card and Bank Transfer may have different implementations of individual steps.
But the overall process should remain:
Validate
↓
Authenticate
↓
Process
↓
Save
↓
Notify
The Template Method Pattern lets the base class enforce this sequence.
5. Real-World Analogy
Consider preparing different types of tea.
The overall recipe is:
Boil Water
↓
Prepare Ingredients
↓
Add Ingredients
↓
Pour into Cup
↓
Serve
For:
Green Tea
Black Tea
Herbal Tea
the sequence is similar, but some steps differ.
The template is:
PrepareTea()
|
+-- BoilWater()
+-- AddIngredients()
+-- Brew()
+-- Serve()
Different tea types customize the appropriate steps.
This is exactly the idea behind Template Method.
6. Template Method Pattern Structure
The basic structure is:
AbstractClass
|
TemplateMethod()
|
+---------------+---------------+
| | |
↓ ↓ ↓
StepOne() StepTwo() StepThree()
↑ ↑ ↑
| | |
ConcreteClassA ConcreteClassB ConcreteClassC
The base class owns the algorithm.
Derived classes own the variable parts.
7. UML Class Diagram
+--------------------------------+
| AbstractProcessor |
+--------------------------------+
| |
| + Process() |
| <<Template Method>> |
| |
| # ReadData() |
| # ValidateData() |
| # TransformData() |
| # SaveData() |
+---------------+----------------+
|
+---------------+---------------+
| |
↓ ↓
+--------------------+ +--------------------+
| CsvProcessor | | JsonProcessor |
+--------------------+ +--------------------+
| ReadData() | | ReadData() |
| ValidateData() | | ValidateData() |
| TransformData() | | TransformData() |
| SaveData() | | SaveData() |
+--------------------+ +--------------------+
The key relationship is:
AbstractClass
↑
|
Concrete Classes
This is an inheritance-based pattern.
8. Components of the Pattern
There are typically three important components.
8.1 Abstract Class
Contains the overall algorithm.
public abstract class DataProcessor
{
public void Process()
{
ReadData();
ValidateData();
TransformData();
SaveData();
}
protected abstract void ReadData();
protected abstract void ValidateData();
protected abstract void TransformData();
protected abstract void SaveData();
}
8.2 Template Method
The method:
public void Process()
is the Template Method.
It defines the sequence:
Read
↓
Validate
↓
Transform
↓
Save
8.3 Concrete Classes
Concrete classes implement individual steps.
public class CsvProcessor : DataProcessor
{
protected override void ReadData()
{
Console.WriteLine("Reading CSV data...");
}
protected override void ValidateData()
{
Console.WriteLine("Validating CSV data...");
}
protected override void TransformData()
{
Console.WriteLine("Transforming CSV data...");
}
protected override void SaveData()
{
Console.WriteLine("Saving CSV data...");
}
}
9. Why Should the Template Method Usually Be Non-Overridable?
A common implementation is:
public void Process()
{
ReadData();
ValidateData();
TransformData();
SaveData();
}
The method is intentionally not marked virtual.
Why?
Because the base class should control the algorithm's sequence.
If derived classes could override it, they could accidentally change:
Read → Validate → Transform → Save
into:
Read → Save → Validate → Transform
Therefore, the Template Method itself is commonly kept stable while individual steps remain customizable.
10. Complete C# Console Application
Let's create a complete example.
Base Class
public abstract class DataProcessor
{
public void Process()
{
Console.WriteLine("Starting processing...");
ReadData();
ValidateData();
TransformData();
if (ShouldSave())
{
SaveData();
}
Notify();
Console.WriteLine("Processing completed.");
}
protected abstract void ReadData();
protected abstract void ValidateData();
protected abstract void TransformData();
protected abstract void SaveData();
protected virtual bool ShouldSave()
{
return true;
}
protected virtual void Notify()
{
Console.WriteLine("Notification sent.");
}
}
Notice something important:
protected virtual bool ShouldSave()
and:
protected virtual void Notify()
These are hook methods.
We'll discuss them shortly.
11. CSV Processor
public class CsvProcessor : DataProcessor
{
protected override void ReadData()
{
Console.WriteLine("Reading CSV file...");
}
protected override void ValidateData()
{
Console.WriteLine("Validating CSV data...");
}
protected override void TransformData()
{
Console.WriteLine("Transforming CSV data...");
}
protected override void SaveData()
{
Console.WriteLine("Saving CSV data to database...");
}
}
12. JSON Processor
public class JsonProcessor : DataProcessor
{
protected override void ReadData()
{
Console.WriteLine("Reading JSON data...");
}
protected override void ValidateData()
{
Console.WriteLine("Validating JSON data...");
}
protected override void TransformData()
{
Console.WriteLine("Transforming JSON data...");
}
protected override void SaveData()
{
Console.WriteLine("Saving JSON data to database...");
}
}
13. Program.cs
var csvProcessor = new CsvProcessor();
csvProcessor.Process();
Console.WriteLine();
var jsonProcessor = new JsonProcessor();
jsonProcessor.Process();
The output will follow the same overall structure:
Starting processing...
Reading CSV file...
Validating CSV data...
Transforming CSV data...
Saving CSV data to database...
Notification sent.
Processing completed.
Starting processing...
Reading JSON data...
Validating JSON data...
Transforming JSON data...
Saving JSON data to database...
Notification sent.
Processing completed.
Notice that the sequence is controlled by the base class.
14. Understanding the Flow
When we call:
csvProcessor.Process();
the method being executed is:
DataProcessor.Process()
The base class executes:
Process()
|
+-- ReadData()
|
+-- ValidateData()
|
+-- TransformData()
|
+-- ShouldSave()
|
+-- SaveData()
|
+-- Notify()
Because of polymorphism, the actual implementation of:
ReadData()
ValidateData()
TransformData()
SaveData()
comes from:
CsvProcessor
This is one of the most important concepts behind the Template Method Pattern.
15. Hook Methods
A hook method is an optional operation that allows subclasses to customize or control part of the algorithm.
For example:
protected virtual bool ShouldSave()
{
return true;
}
A subclass can override it:
protected override bool ShouldSave()
{
return false;
}
Now the Template Method:
if (ShouldSave())
{
SaveData();
}
can conditionally skip saving.
16. Abstract Methods vs Hook Methods
This distinction is important.
Abstract Method
Must be implemented by derived classes.
protected abstract void ReadData();
Virtual Hook
Provides a default implementation.
protected virtual void Notify()
{
Console.WriteLine("Notification sent.");
}
Derived classes can override it if required.
Summary
Abstract Method
↓
Mandatory customization
Hook Method
↓
Optional customization
17. Payment Processing Example
Now let's consider a more realistic enterprise scenario.
Suppose multiple payment types follow this workflow:
Validate Request
↓
Authenticate
↓
Check Fraud
↓
Process Payment
↓
Save Transaction
↓
Send Notification
Credit Card:
Validate
Authenticate
Fraud Check
Credit Card Processing
Save
Notify
Bank Transfer:
Validate
Authenticate
Fraud Check
Bank Transfer Processing
Save
Notify
The workflow remains the same.
Only certain steps change.
18. Payment Template
public abstract class PaymentProcessor
{
public void ProcessPayment(decimal amount)
{
Validate(amount);
Authenticate();
PerformFraudCheck();
Process(amount);
SaveTransaction(amount);
if (ShouldNotify())
{
SendNotification();
}
}
protected abstract void Validate(decimal amount);
protected abstract void Authenticate();
protected virtual void PerformFraudCheck()
{
Console.WriteLine("Performing standard fraud check...");
}
protected abstract void Process(decimal amount);
protected virtual void SaveTransaction(decimal amount)
{
Console.WriteLine(
$"Saving transaction of ${amount}...");
}
protected virtual bool ShouldNotify()
{
return true;
}
protected virtual void SendNotification()
{
Console.WriteLine("Payment notification sent.");
}
}
19. Credit Card Processor
public class CreditCardPaymentProcessor
: PaymentProcessor
{
protected override void Validate(decimal amount)
{
Console.WriteLine(
$"Validating credit card payment: ${amount}");
}
protected override void Authenticate()
{
Console.WriteLine(
"Authenticating credit card...");
}
protected override void Process(decimal amount)
{
Console.WriteLine(
$"Processing credit card payment: ${amount}");
}
}
20. Bank Transfer Processor
public class BankTransferPaymentProcessor
: PaymentProcessor
{
protected override void Validate(decimal amount)
{
Console.WriteLine(
$"Validating bank transfer: ${amount}");
}
protected override void Authenticate()
{
Console.WriteLine(
"Authenticating bank account...");
}
protected override void Process(decimal amount)
{
Console.WriteLine(
$"Processing bank transfer: ${amount}");
}
}
Usage:
var creditCard =
new CreditCardPaymentProcessor();
creditCard.ProcessPayment(500);
Console.WriteLine();
var bankTransfer =
new BankTransferPaymentProcessor();
bankTransfer.ProcessPayment(1000);
21. ASP.NET Core Implementation
Now let's see how this pattern can be applied to an ASP.NET Core application.
Suppose we are building a document-processing API.
Supported documents:
PDF
CSV
JSON
XML
The processing pipeline is:
Receive Document
↓
Validate
↓
Parse
↓
Transform
↓
Save
↓
Audit
22. Abstract Document Processor
public abstract class DocumentProcessor
{
public async Task ProcessAsync(
Stream document)
{
await ValidateAsync(document);
await ParseAsync(document);
await TransformAsync();
await SaveAsync();
await AuditAsync();
}
protected abstract Task ValidateAsync(
Stream document);
protected abstract Task ParseAsync(
Stream document);
protected abstract Task TransformAsync();
protected abstract Task SaveAsync();
protected virtual Task AuditAsync()
{
Console.WriteLine(
"Document processing audited.");
return Task.CompletedTask;
}
}
The workflow is centralized.
23. PDF Processor
public class PdfDocumentProcessor
: DocumentProcessor
{
protected override Task ValidateAsync(
Stream document)
{
Console.WriteLine(
"Validating PDF document.");
return Task.CompletedTask;
}
protected override Task ParseAsync(
Stream document)
{
Console.WriteLine(
"Parsing PDF document.");
return Task.CompletedTask;
}
protected override Task TransformAsync()
{
Console.WriteLine(
"Transforming PDF content.");
return Task.CompletedTask;
}
protected override Task SaveAsync()
{
Console.WriteLine(
"Saving PDF data.");
return Task.CompletedTask;
}
}
24. JSON Processor
public class JsonDocumentProcessor
: DocumentProcessor
{
protected override Task ValidateAsync(
Stream document)
{
Console.WriteLine(
"Validating JSON document.");
return Task.CompletedTask;
}
protected override Task ParseAsync(
Stream document)
{
Console.WriteLine(
"Parsing JSON document.");
return Task.CompletedTask;
}
protected override Task TransformAsync()
{
Console.WriteLine(
"Transforming JSON content.");
return Task.CompletedTask;
}
protected override Task SaveAsync()
{
Console.WriteLine(
"Saving JSON data.");
return Task.CompletedTask;
}
}
25. ASP.NET Core Controller
A controller could receive a document and select an appropriate processor.
[ApiController]
[Route("api/documents")]
public class DocumentsController : ControllerBase
{
[HttpPost("{type}")]
public async Task<IActionResult> Process(
string type,
IFormFile file)
{
DocumentProcessor processor;
if (type.Equals(
"pdf",
StringComparison.OrdinalIgnoreCase))
{
processor =
new PdfDocumentProcessor();
}
else if (type.Equals(
"json",
StringComparison.OrdinalIgnoreCase))
{
processor =
new JsonDocumentProcessor();
}
else
{
return BadRequest(
"Unsupported document type.");
}
await using var stream =
file.OpenReadStream();
await processor.ProcessAsync(stream);
return Ok(
"Document processed successfully.");
}
}
However, in a production ASP.NET Core application, we would generally avoid creating concrete implementations directly inside the controller.
Dependency Injection and a resolver/factory can provide cleaner architecture.
26. Template Method with Dependency Injection
Suppose the base class needs services such as:
ILogger
Repository
Audit Service
Storage Service
We can inject dependencies through constructors.
public abstract class DocumentProcessor
{
protected readonly ILogger Logger;
protected DocumentProcessor(
ILogger logger)
{
Logger = logger;
}
public async Task ProcessAsync(
Stream document)
{
await ValidateAsync(document);
await ParseAsync(document);
await TransformAsync();
await SaveAsync();
}
protected abstract Task ValidateAsync(
Stream document);
protected abstract Task ParseAsync(
Stream document);
protected abstract Task TransformAsync();
protected abstract Task SaveAsync();
}
A derived class can receive its own dependencies.
public class PdfDocumentProcessor
: DocumentProcessor
{
public PdfDocumentProcessor(
ILogger<PdfDocumentProcessor> logger)
: base(logger)
{
}
protected override Task ValidateAsync(
Stream document)
{
Logger.LogInformation(
"Validating PDF...");
return Task.CompletedTask;
}
protected override Task ParseAsync(
Stream document)
{
Logger.LogInformation(
"Parsing PDF...");
return Task.CompletedTask;
}
protected override Task TransformAsync()
{
Logger.LogInformation(
"Transforming PDF...");
return Task.CompletedTask;
}
protected override Task SaveAsync()
{
Logger.LogInformation(
"Saving PDF...");
return Task.CompletedTask;
}
}
27. Template Method and Dependency Injection – Important Point
The Template Method Pattern itself is based primarily on:
Inheritance
Abstract Classes
Polymorphism
Dependency Injection is complementary.
You can combine them:
ASP.NET Core
↓
Dependency Injection
↓
Concrete Processor
↓
Template Method
↓
Overridden Steps
This can be useful in enterprise applications where processing steps require repositories, logging, configuration, APIs, or other infrastructure services.
28. Real-World Enterprise Example – ETL
ETL stands for:
Extract
Transform
Load
A generic ETL workflow might be:
Extract Data
↓
Validate
↓
Transform
↓
Load
↓
Audit
Different implementations may extract from:
SQL Server
CSV
REST API
Azure Storage
External Database
Template Method can define:
public void Execute()
{
Extract();
Validate();
Transform();
Load();
Audit();
}
while subclasses customize individual operations.
29. Real-World Example – Report Generation
Suppose an enterprise application creates:
Sales Report
Inventory Report
Customer Report
Financial Report
The common workflow may be:
Load Data
↓
Validate
↓
Prepare Model
↓
Generate Report
↓
Save Report
↓
Notify User
Template Method can define the workflow while each report type implements the specific steps.
30. Real-World Example – Authentication
An authentication pipeline might contain:
Receive Credentials
↓
Validate
↓
Authenticate
↓
Load User
↓
Create Session/Token
↓
Audit
Different authentication mechanisms might customize the authentication step:
Password
Certificate
External Identity Provider
Corporate Authentication
However, authentication systems often benefit from other patterns and framework abstractions too, so Template Method should be applied only when the workflow genuinely fits inheritance-based customization.
31. Template Method vs Strategy
This is one of the most frequently asked interview questions.
Both patterns can solve algorithm variation, but they do it differently.
Template Method
Uses:
Inheritance
Structure:
Base Class
↓
Derived Class
The base class controls the workflow.
Strategy
Uses:
Composition
Structure:
Context
↓
Strategy Interface
↓
Concrete Strategy
The strategy can be replaced at runtime.
Simple comparison
Template Method
→ Define algorithm skeleton
→ Customize selected steps
→ Inheritance
→ Compile-time class structure
Strategy
→ Encapsulate complete algorithms
→ Replace algorithm independently
→ Composition
→ Runtime interchangeability
32. Template Method vs Factory Method
These two patterns are related.
Factory Method focuses on:
Creating an object.
Template Method focuses on:
Defining the steps of an algorithm.
For example:
Factory Method
↓
Create PaymentProcessor
Template Method:
PaymentProcessor
↓
Validate
Authenticate
Process
Save
Notify
They can also be used together.
33. Template Method vs State
Template Method
Behavior is organized around an algorithm's steps.
Process
↓
Step 1
↓
Step 2
↓
Step 3
State
Behavior changes based on the object's current state.
Order
↓
Pending
↓
Confirmed
↓
Shipped
↓
Delivered
Simple rule:
Template Method controls an algorithm; State controls behavior based on state.
34. Advantages
1. Eliminates Duplicate Workflow Code
The algorithm skeleton is centralized.
2. Controls Algorithm Structure
The base class controls the order of execution.
3. Promotes Code Reuse
Common logic is implemented once.
4. Supports the Open/Closed Principle
The workflow can remain stable while new implementations are added.
5. Encourages Consistency
All derived classes follow the same processing sequence.
6. Supports Hooks
Optional behavior can be customized.
7. Easy to Understand
The algorithm structure is visible in one place.
35. Disadvantages
1. Uses Inheritance
Inheritance introduces coupling between base and derived classes.
2. Can Violate Liskov Substitution Principle if Poorly Designed
Derived classes must genuinely represent valid variations of the base abstraction.
3. Difficult to Change Algorithm Structure Dynamically
If the workflow itself needs runtime replacement, Strategy may be more appropriate.
4. Base Class Can Become Too Large
Too many steps and hooks can make the base class complicated.
5. Inheritance Hierarchies Can Become Deep
Avoid unnecessary levels of inheritance.
36. Best Practices
1. Keep the Template Method Focused
Avoid creating a massive method with dozens of steps.
2. Keep Common Logic in the Base Class
Only common workflow belongs there.
3. Keep Variable Logic in Derived Classes
Don't put concrete implementation details into the base class unnecessarily.
4. Use protected for Extension Points
For example:
protected abstract void Process();
This prevents external callers from invoking individual internal steps directly.
5. Keep the Template Method Stable
Avoid allowing derived classes to completely replace the algorithm sequence.
6. Use Hook Methods Carefully
Hooks are useful for optional behavior, but too many hooks can make the base class difficult to understand.
7. Prefer Composition When Runtime Flexibility Is Required
If algorithms need to change dynamically, consider Strategy.
8. Follow SOLID Principles
Especially:
Single Responsibility
Open/Closed Principle
Liskov Substitution Principle
37. Common Mistakes
Mistake 1 – Making Everything Abstract
Not every method needs to be abstract.
Common behavior should remain in the base class.
Mistake 2 – Allowing Derived Classes to Change the Entire Workflow
The purpose is for the base class to control the algorithm.
Mistake 3 – Too Many Hooks
An excessive number of hooks can make the pattern difficult to understand.
Mistake 4 – Deep Inheritance
Avoid:
Base
↓
Level1
↓
Level2
↓
Level3
↓
Level4
Prefer a shallow hierarchy.
Mistake 5 – Using Template Method When Strategy Is Better
If the entire algorithm needs to be interchangeable, Strategy may be a better fit.
38. Unit Testing
The individual derived classes can be tested independently.
For example:
[Fact]
public async Task CsvProcessor_ShouldProcessSuccessfully()
{
var processor =
new CsvProcessor();
processor.Process();
// Assert expected behavior.
}
For real applications, dependencies such as repositories and external services should be mocked.
For example:
CsvProcessor
↓
Repository Mock
↓
Storage Mock
↓
Logger Mock
This makes individual implementations easier to test.
39. Interview Questions
Beginner
1. What is the Template Method Design Pattern?
It defines the skeleton of an algorithm in a base class and allows subclasses to customize individual steps.
2. Which category does Template Method belong to?
It is a Behavioral Design Pattern.
3. Which OOP concept does it primarily use?
Inheritance and polymorphism.
4. What is a Template Method?
A method in the base class that defines the overall algorithm sequence.
5. What is a hook method?
An optional method that subclasses can override to customize behavior.
Intermediate
6. Why is Template Method often implemented using an abstract class?
Because the pattern requires a common algorithm skeleton and extension points that derived classes can customize.
7. Why should the Template Method generally not be overridden?
Because the base class should control the sequence of the algorithm.
8. What is the difference between abstract methods and hook methods?
Abstract methods require subclasses to provide an implementation.
Hook methods usually provide default behavior and allow optional customization.
9. Template Method vs Strategy?
Template Method uses inheritance.
Strategy uses composition.
10. Template Method vs Factory Method?
Template Method defines an algorithm.
Factory Method defines how an object is created.
Advanced
11. Can Template Method use Dependency Injection?
Yes. Derived classes can receive services through DI while the base class controls the workflow.
12. Does Template Method violate the Open/Closed Principle?
It can support OCP when new implementations extend the base abstraction without changing the existing algorithm.
However, poor inheritance design can create coupling and make changes difficult.
13. How can you prevent subclasses from changing the algorithm?
Keep the Template Method non-overridable and expose only the intended extension points.
14. When would you choose Strategy instead?
Choose Strategy when you need to switch complete algorithms at runtime and want composition rather than inheritance.
15. Can Template Method and Factory Method be used together?
Yes.
A Factory Method can create the appropriate processor, and the Template Method can control how that processor executes its workflow.
16. What are the disadvantages of Template Method?
Inheritance coupling
Potentially large base classes
Deep inheritance hierarchies
Limited runtime flexibility
17. What is the Hollywood Principle in Template Method?
A common principle associated with the pattern is:
"Don't call us, we'll call you."
The base class controls the flow and calls the overridden operations at the appropriate points.
18. Why is Template Method called a behavioral pattern?
Because it defines how an algorithm's behavior is organized and how responsibilities are distributed between a base class and subclasses.
40. When Should You Use Template Method?
Use it when:
Multiple algorithms follow the same overall workflow.
Several classes share common processing steps.
The sequence of operations must remain consistent.
Some steps vary between implementations.
You want to avoid duplicated workflow code.
The algorithm structure should be controlled centrally.
Inheritance is appropriate for the relationship.
Examples:
Data Import
ETL Processing
Report Generation
Document Processing
Payment Workflows
File Processing
Batch Processing
Order Processing
41. When Should You Avoid Template Method?
Avoid it when:
Algorithms are completely unrelated.
The complete algorithm needs runtime replacement.
Inheritance is not appropriate.
The base class would become too complex.
There are only minor differences that don't justify an abstraction.
Composition would provide a cleaner architecture.
In these situations, consider:
Strategy
Composition
Dependency Injection
Pipeline
Chain of Responsibility
42. Template Method in a Modern .NET Architecture
A practical architecture could look like:
ASP.NET Core API
|
↓
Application Service
|
↓
Processor Factory
|
+------------+------------+
| | |
↓ ↓ ↓
CSV JSON XML
Processor Processor Processor
\ | /
\ | /
+----------+----------+
|
Template Method
|
+--------------+--------------+
| | |
Validate Transform Save
This architecture can be useful when all processors share a stable workflow but differ in individual steps.
43. Template Method vs Strategy – Quick Comparison
| Feature | Template Method | Strategy |
|---|---|---|
| Pattern Type | Behavioral | Behavioral |
| Main Goal | Define algorithm skeleton | Encapsulate algorithms |
| Main Mechanism | Inheritance | Composition |
| Base Class | Usually required | Not required |
| Runtime Switching | Limited | Excellent |
| Code Reuse | High | Moderate |
| Coupling | Higher | Lower |
| Hooks | Common | Not typical |
| Best For | Same workflow, variable steps | Completely interchangeable algorithms |
44. Simple Memory Trick
Remember:
TEMPLATE METHOD
↓
"WHAT ARE THE STEPS?"
↓
Base Class controls the workflow
Whereas:
STRATEGY
↓
"WHICH ALGORITHM?"
↓
Choose an implementation
For example:
Template Method:
Validate
↓
Process
↓
Save
↓
Notify
Strategy:
Process using:
Credit Card
OR
PayPal
OR
Bank Transfer
45. Complete Concept in One Diagram
TEMPLATE METHOD
|
↓
AbstractProcessor
|
Process()
|
+---------------+---------------+
| | |
↓ ↓ ↓
Validate Process Save
| | |
+---------------+---------------+
|
↓
Concrete Processor
|
+-------------+-------------+
| |
↓ ↓
CsvProcessor JsonProcessor
| |
CSV implementation JSON implementation
The important point is:
The base class defines the workflow; subclasses provide the variable steps.
46. Key Takeaways
Remember these important points:
1. Template Method is a Behavioral Pattern
It focuses on organizing algorithm behavior.
2. It uses inheritance
The base class defines the algorithm and derived classes customize steps.
3. The Template Method defines the algorithm skeleton
For example:
Read
↓
Validate
↓
Transform
↓
Save
4. Abstract methods represent required variation
protected abstract void Process();
5. Hook methods represent optional variation
protected virtual bool ShouldSave()
6. The base class controls the workflow
This keeps implementations consistent.
7. Template Method and Strategy are different
Template Method
→ Same workflow, different steps
Strategy
→ Different interchangeable algorithms
8. Don't overuse inheritance
If composition provides a cleaner solution, consider Strategy or another pattern.
Conclusion
The Template Method Design Pattern is an excellent choice when multiple processes follow the same overall algorithm but have different implementations for individual steps.
Instead of duplicating:
Validate
Process
Save
Notify
across many classes, we define the workflow once:
public void Process()
{
Validate();
ProcessData();
Save();
Notify();
}
and allow subclasses to customize the individual operations.
The overall architecture becomes:
Base Class
|
Template Method
|
+------------+------------+
| | |
↓ ↓ ↓
Step A Step B Step C
↑ ↑ ↑
| | |
Concrete Concrete Concrete
Class A Class B Class C
This provides code reuse, consistency, extensibility, and centralized workflow management.
However, remember that Template Method relies on inheritance. If your application needs complete algorithms to be dynamically interchangeable, the Strategy Pattern may be a better choice.
The key lesson is:
Use Template Method when the overall algorithm is fixed but certain steps need to vary between implementations.
🚀 Coming Up Next: Part 4.11 – Visitor Design Pattern
In the final article of the Behavioral Design Patterns section, we'll explore the Visitor Design Pattern, including:
What is the Visitor Pattern?
Why do we need it?
Visitor and Element concepts
Double Dispatch
IVisitorandAccept()UML Class Diagram
Complete C# Console Application
Document processing example
Shopping cart example
Tax calculation example
ASP.NET Core implementation
Real-world enterprise scenarios
Advantages and disadvantages
Best practices
Common mistakes
Visitor vs Strategy
Visitor vs Composite
Visitor vs Interpreter
Visitor vs Decorator
Interview questions
The Visitor Pattern is particularly useful when you need to perform multiple operations across a stable object structure without repeatedly modifying the classes that represent that structure.
Visitor Design Pattern
No comments:
Post a Comment