Thursday, July 30, 2026

.NET Interview Preparation Guide For Experience(Lead)

.NET Lead Interview Preparation Guide

1. What the interviewer is likely looking for


AreaExpected Skills
.NET / C#Advanced C#, OOP, SOLID, async/await, LINQ, collections, generics
ASP.NET CoreWeb API, Middleware, DI, Filters, Configuration, Logging
REST APIsREST principles, HTTP methods, status codes, authentication, versioning
ArchitectureClean Architecture, Layered Architecture, Microservices, DDD
Design PatternsFactory, Singleton, Strategy, Repository, Unit of Work, CQRS, Mediator
DatabaseSQL Server, EF Core, indexing, transactions, performance tuning
PerformanceCaching, async programming, database optimization, scalability
CloudAzure/AWS concepts, App Service, Functions, Service Bus, Key Vault
DevOpsGit, CI/CD, Docker, Kubernetes, Azure DevOps
TestingUnit testing, integration testing, mocking
LeadershipCode reviews, mentoring, estimation, architecture decisions
AgileScrum, sprint planning, backlog, technical discussions
System DesignHigh availability, scalability, resiliency, distributed systems

Part 1 – .NET and C# Interview Questions

Q1. What is the difference between .NET Framework and modern .NET?

Answer:

.NET Framework is the older Windows-focused framework, while modern .NET is cross-platform and supports Windows, Linux, and macOS.

Modern .NET provides:

  • Cross-platform development

  • High performance

  • Built-in dependency injection

  • ASP.NET Core

  • Cloud-native development

  • Container support

  • Minimal APIs

  • Improved CLI tooling

For a new enterprise application, modern .NET is generally preferred.


Q2. What are the important features introduced in modern C#?

Important features include:

  • Async/await

  • Pattern matching

  • Nullable reference types

  • Records

  • Tuples

  • Expression-bodied members

  • Local functions

  • LINQ

  • Generics

  • init properties

  • required members

  • Improved pattern matching

A Lead should understand not only the syntax but when each feature should be used.


Q3. Explain SOLID principles.

S – Single Responsibility Principle

A class should have one primary responsibility.

O – Open/Closed Principle

Classes should be open for extension but closed for modification.

L – Liskov Substitution Principle

Derived classes should be substitutable for their base classes.

I – Interface Segregation Principle

Clients should not be forced to depend on methods they don't use.

D – Dependency Inversion Principle

High-level modules should depend on abstractions rather than concrete implementations.

Lead-level follow-up

The interviewer may ask:

"How have you applied SOLID principles in your project?"

You should provide an actual project example involving services, repositories, interfaces, and dependency injection.


Part 2 – ASP.NET Core

Q4. Explain ASP.NET Core request pipeline.

A typical request goes through:

Client
   ↓
Load Balancer
   ↓
Web Server
   ↓
Middleware Pipeline
   ↓
Routing
   ↓
Authentication
   ↓
Authorization
   ↓
Controller
   ↓
Service
   ↓
Repository / EF Core
   ↓
Database

Middleware can perform tasks such as:

  • Exception handling

  • Logging

  • Authentication

  • Authorization

  • CORS

  • Routing

  • Response processing


Q5. What is middleware?

Middleware is software that participates in processing HTTP requests and responses.

Example:

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

Custom middleware:

public class RequestLoggingMiddleware
{
    private readonly RequestDelegate _next;

    public RequestLoggingMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        Console.WriteLine(
            $"{context.Request.Method} {context.Request.Path}");

        await _next(context);
    }
}

Part 3 – Dependency Injection

Q6. Explain Singleton, Scoped and Transient lifetimes.

LifetimeDescription
SingletonOne instance for application lifetime
ScopedOne instance per HTTP request
TransientNew instance whenever requested

Example:

builder.Services.AddSingleton<ICacheService, CacheService>();

builder.Services.AddScoped<IOrderService, OrderService>();

builder.Services.AddTransient<IEmailService, EmailService>();

Lead-level question

Why should DbContext generally be registered as Scoped?

Because DbContext represents a unit of work and is designed to manage a request-level set of operations. Sharing one instance across unrelated requests can introduce concurrency and state-management problems.


Part 4 – REST API

Q7. What are the characteristics of a RESTful API?

Important characteristics:

  • Stateless communication

  • Resource-oriented URLs

  • HTTP methods

  • Standard HTTP status codes

  • JSON/XML representations

  • Cacheability where appropriate

  • Separation between client and server

Example:

GET    /api/orders
GET    /api/orders/1001
POST   /api/orders
PUT    /api/orders/1001
PATCH  /api/orders/1001
DELETE /api/orders/1001

Q8. Difference between PUT and PATCH?

PUT

Usually represents replacement of the resource.

PUT /api/customers/100

PATCH

Used for partial modification.

PATCH /api/customers/100

For example, updating only the customer's phone number.


Q9. What HTTP status codes should a .NET API return?

Common examples:

200 OK
201 Created
204 No Content
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
409 Conflict
422 Unprocessable Content
500 Internal Server Error

A Lead should ensure that APIs consistently use meaningful status codes.


Part 5 – API Security

Q10. How would you secure a REST API?

I would consider:

HTTPS
 ↓
Authentication
 ↓
JWT / OAuth 2.0 / OpenID Connect
 ↓
Authorization
 ↓
Role/Policy-based access
 ↓
Input validation
 ↓
Rate limiting
 ↓
Logging & monitoring

Additional considerations:

  • Don't expose secrets in source code.

  • Store secrets securely.

  • Validate input.

  • Apply least privilege.

  • Avoid returning sensitive information.

  • Implement appropriate CORS policies.

  • Protect APIs against abuse.


Part 6 – Entity Framework Core

Q11. What is Entity Framework Core?

EF Core is Microsoft's ORM for .NET.

It allows developers to work with databases using .NET objects rather than writing SQL for every operation.

Example:

var customers = await dbContext.Customers
    .Where(x => x.IsActive)
    .OrderBy(x => x.Name)
    .ToListAsync();

Q12. How do you optimize EF Core queries?

Important techniques:

  1. Use AsNoTracking() for read-only queries.

  2. Select only required columns.

  3. Avoid unnecessary Include().

  4. Use pagination.

  5. Avoid N+1 queries.

  6. Use proper database indexes.

  7. Use asynchronous database operations.

  8. Examine generated SQL.

  9. Avoid loading huge datasets into memory.

  10. Consider compiled queries for suitable high-throughput scenarios.

Example:

var customers = await dbContext.Customers
    .AsNoTracking()
    .Where(x => x.IsActive)
    .Select(x => new CustomerDto
    {
        Id = x.Id,
        Name = x.Name
    })
    .ToListAsync();

Part 7 – SQL Server

Q13. How do you optimize a slow SQL query?

As a Lead, I would investigate systematically:

Slow Query
   ↓
Execution Plan
   ↓
Indexes
   ↓
Statistics
   ↓
Joins
   ↓
WHERE conditions
   ↓
Data volume
   ↓
Blocking / Deadlocks
   ↓
Query redesign

I would examine:

  • Execution plan

  • Index usage

  • Table scans

  • Key lookups

  • Expensive joins

  • Missing indexes

  • Statistics

  • Blocking

  • Deadlocks

  • Parameterization

  • Query cardinality


Part 8 – Async/Await

Q14. Why is async/await important in Web APIs?

ASP.NET Core applications frequently perform I/O operations:

  • Database calls

  • HTTP calls

  • File operations

  • Messaging

  • External services

Async programming allows threads to be released while waiting for I/O.

Example:

public async Task<OrderDto?> GetOrderAsync(int id)
{
    return await _dbContext.Orders
        .AsNoTracking()
        .Where(x => x.Id == id)
        .Select(x => new OrderDto
        {
            Id = x.Id,
            Amount = x.Amount
        })
        .FirstOrDefaultAsync();
}

Interview follow-up

Question: Should we use .Result or .Wait()?

Generally, avoid blocking on asynchronous operations. Prefer async all the way through the call chain.


Part 9 – Architecture

Q15. What architecture would you choose for a large .NET application?

For a typical enterprise system, I might use:

Angular / React
       ↓
API Gateway / Load Balancer
       ↓
ASP.NET Core Web API
       ↓
Application Layer
       ↓
Domain Layer
       ↓
Infrastructure Layer
       ↓
SQL Server / External Services

With:

Authentication
Logging
Caching
Messaging
Monitoring
Exception Handling
Validation

Depending on the requirements, I might choose:

  • Clean Architecture

  • Modular Monolith

  • Microservices

  • DDD

  • CQRS

  • Event-driven architecture

The key is not to choose microservices simply because they are popular.


Part 10 – Microservices

Q16. When would you choose Microservices?

I would consider microservices when there are strong reasons such as:

  • Independent deployment requirements

  • Different scaling requirements

  • Clearly separated business capabilities

  • Independent team ownership

  • Technology or lifecycle differences

  • Need for fault isolation

But microservices introduce complexity:

  • Distributed transactions

  • Network failures

  • Observability

  • Service discovery

  • Deployment complexity

  • Data consistency

  • Distributed tracing

Therefore, I would evaluate the business and operational requirements before adopting them.


Part 11 – CQRS

Q17. What is CQRS?

CQRS means:

Command Query Responsibility Segregation

It separates:

Commands → Change State
Queries  → Read State

Example:

CreateOrderCommand
UpdateOrderCommand
CancelOrderCommand

GetOrderQuery
GetOrdersQuery

Benefits can include:

  • Clear separation of responsibilities

  • Independent optimization of reads and writes

  • Better scalability in suitable systems

  • Cleaner business workflows

CQRS doesn't necessarily mean two databases.


Part 12 – Design Patterns

Q18. Which design patterns have you used?

A strong .NET Lead answer could include:

Creational
 ├── Factory
 ├── Abstract Factory
 └── Builder

Structural
 ├── Adapter
 ├── Decorator
 ├── Facade
 └── Proxy

Behavioral
 ├── Strategy
 ├── Observer
 ├── Command
 └── Mediator

Enterprise patterns:

Repository
Unit of Work
CQRS
Specification
Dependency Injection

The interviewer may then ask:

"Give me a real-world example of each."

Prepare at least one practical example for each major pattern.


Part 13 – Caching

Q19. How would you improve API performance using caching?

Possible approaches:

Client Cache
     ↓
CDN
     ↓
Distributed Cache
     ↓
Application
     ↓
Database

For distributed systems, a distributed cache such as Redis can be useful.

Typical cached data:

  • Reference data

  • Configuration

  • Product catalogs

  • Frequently accessed queries

  • Session-related information where appropriate

But caching introduces challenges:

  • Cache invalidation

  • Stale data

  • Memory usage

  • Cache consistency


Part 14 – Azure

Q20. Which Azure services are commonly used in .NET applications?

A .NET Lead should be familiar with services such as:

Azure App Service
Azure Functions
Azure SQL Database
Azure Storage
Azure Service Bus
Azure Key Vault
Azure Container Registry
Azure Kubernetes Service
Azure API Management
Azure Monitor
Application Insights
Azure DevOps

A typical architecture:

Angular
   ↓
Azure Front Door
   ↓
API Management
   ↓
ASP.NET Core APIs
   ↓
Azure SQL
   ↓
Service Bus
   ↓
Background Worker

Part 15 – Messaging

Q21. Queue vs Topic?

Queue

Producer
   ↓
Queue
   ↓
Consumer

Generally, a message is processed by one competing consumer.

Topic

              → Subscriber A
Producer → Topic
              → Subscriber B
              → Subscriber C

Useful when multiple independent subscribers need to receive an event.


Part 16 – Resilience

Q22. How would you design a resilient API?

Consider:

  • Retry

  • Timeout

  • Circuit breaker

  • Rate limiting

  • Bulkhead isolation

  • Health checks

  • Graceful degradation

  • Idempotency

  • Logging

  • Distributed tracing

Example:

API
 ↓
External Service
 ↓
Timeout
 ↓
Retry
 ↓
Circuit Breaker
 ↓
Fallback

Don't blindly retry every failure. Retry policies should distinguish transient failures from permanent failures.


Part 17 – Docker

Q23. Why use Docker with .NET?

Docker provides:

  • Consistent environments

  • Easy deployment

  • Isolation

  • Portability

  • Container-based scaling

Typical flow:

.NET Application
       ↓
Dockerfile
       ↓
Docker Image
       ↓
Container Registry
       ↓
Kubernetes / App Service

Part 18 – Testing

Q24. What testing strategy would you use?

I would use multiple levels:

Unit Tests
    ↓
Integration Tests
    ↓
API Tests
    ↓
End-to-End Tests

For unit tests:

  • xUnit / NUnit

  • Moq or another mocking framework

  • FluentAssertions where appropriate

A Lead should encourage developers to test business logic rather than simply chasing code coverage numbers.


Part 19 – Logging and Monitoring

Q25. How would you troubleshoot a production issue?

My approach would be:

Alert
 ↓
Application Logs
 ↓
Correlation ID
 ↓
Distributed Trace
 ↓
API Metrics
 ↓
Database Metrics
 ↓
External Dependencies
 ↓
Root Cause
 ↓
Fix
 ↓
Post-Incident Review

I would avoid debugging production issues solely through Console.WriteLine().

Structured logging and observability are important in distributed systems.


Part 20 – Lead-Level System Design Question

Q26. Design a scalable e-commerce backend.

A good answer could look like:

                  Angular
                     |
                     ↓
             Azure Front Door
                     |
                     ↓
              API Management
                     |
        ┌────────────┼────────────┐
        ↓            ↓            ↓
     Catalog       Order        Customer
     Service       Service       Service
        |            |             |
        ↓            ↓             ↓
      Cache        SQL DB        SQL DB
                     |
                     ↓
                Service Bus
                     |
          ┌──────────┼──────────┐
          ↓          ↓          ↓
       Payment     Email      Inventory
       Service     Service      Service

Then discuss:

  • Authentication

  • Authorization

  • API versioning

  • Database design

  • Caching

  • Messaging

  • Retry

  • Circuit breaker

  • Idempotency

  • Logging

  • Monitoring

  • Scaling

  • Disaster recovery

This is exactly the type of discussion expected from a Lead-level candidate.


Part 21 – Leadership Questions

These are extremely important because this is a .NET Lead position.

Q27. How do you conduct code reviews?

Good answer:

I focus on correctness, maintainability, security, performance, testability, and consistency with architectural standards. I try to keep code reviews objective and constructive. I distinguish between mandatory changes and suggestions so that reviews don't become unnecessarily subjective.


Q28. How do you handle disagreements with developers?

I first understand the developer's reasoning. I then compare both approaches against requirements, maintainability, performance, security, and architectural principles. If necessary, I use a proof of concept or measurable data rather than relying on personal preference. The objective is to reach the best technical decision for the product.


Q29. What do you do when a developer repeatedly produces poor-quality code?

A strong Lead answer:

  1. Understand the underlying problem.

  2. Provide specific feedback.

  3. Pair with the developer if needed.

  4. Establish coding standards.

  5. Provide mentoring.

  6. Review progress.

  7. Escalate through the appropriate management process if the issue continues.

Avoid saying:

"I would immediately report the developer."

A Lead is expected to coach before escalating.


Part 22 – Project Management / Agile

Q30. How do you handle unrealistic deadlines?

First, I understand the business priority and deadline. Then I break the requirement into smaller deliverables and identify dependencies and risks. I provide estimates based on technical complexity and team capacity. If the deadline is fixed, I discuss scope prioritization rather than compromising critical quality, security, or reliability requirements.

This is a very important Lead-level answer.


Part 23 – Real-World Scenario Questions

Prepare for these carefully.

Scenario 1

Production API suddenly becomes slow. What will you do?

Answer structure:

1. Check monitoring
2. Identify affected API
3. Check response time
4. Check database
5. Check external dependencies
6. Check recent deployments
7. Check logs/traces
8. Check CPU/memory
9. Identify bottleneck
10. Mitigate
11. Fix root cause
12. Post-incident analysis

Scenario 2

Database is taking 10 seconds for an API request. What do you investigate?

API
 ↓
Service
 ↓
EF Core
 ↓
Generated SQL
 ↓
Execution Plan
 ↓
Indexes
 ↓
Statistics
 ↓
Blocking
 ↓
Data Volume

Scenario 3

Two developers disagree about architecture. What will you do?

Use:

Understand both solutions
        ↓
Identify requirements
        ↓
Compare trade-offs
        ↓
Performance
Security
Maintainability
Scalability
Cost
        ↓
POC if necessary
        ↓
Decision
        ↓
Document ADR

Part 24 – Coding Questions

Expect coding exercises such as:

C# LINQ

var result = employees
    .Where(e => e.IsActive)
    .GroupBy(e => e.Department)
    .Select(g => new
    {
        Department = g.Key,
        Count = g.Count(),
        AverageSalary = g.Average(x => x.Salary)
    });

Async Programming

public async Task<IEnumerable<Product>> GetProductsAsync()
{
    return await _context.Products
        .AsNoTracking()
        .Where(x => x.IsActive)
        .ToListAsync();
}

Dependency Injection

Be prepared to implement:

Controller
   ↓
Service Interface
   ↓
Service
   ↓
Repository Interface
   ↓
Repository
   ↓
EF Core
   ↓
SQL Server

Part 25 – .NET Lead Rapid-Fire Questions

You should be able to answer these quickly:

  1. What is Dependency Injection?

  2. Singleton vs Scoped vs Transient?

  3. Middleware vs Filters?

  4. Authentication vs Authorization?

  5. IEnumerable vs IQueryable?

  6. IEnumerable vs IAsyncEnumerable?

  7. Task vs ValueTask?

  8. async/await?

  9. ConfigureAwait?

  10. First() vs FirstOrDefault()?

  11. Single() vs SingleOrDefault()?

  12. String vs StringBuilder?

  13. record vs class?

  14. Value type vs reference type?

  15. Boxing vs unboxing?

  16. What is garbage collection?

  17. What is IDisposable?

  18. What is middleware?

  19. What are filters?

  20. What is model binding?

  21. What is API versioning?

  22. What is CORS?

  23. What is JWT?

  24. What is OAuth 2.0?

  25. What is OpenID Connect?

  26. What is EF Core tracking?

  27. What is AsNoTracking()?

  28. What is eager loading?

  29. What is lazy loading?

  30. What is N+1 problem?

  31. What is database indexing?

  32. Clustered vs non-clustered index?

  33. What is deadlock?

  34. What is blocking?

  35. What is transaction isolation?

  36. What is CQRS?

  37. What is DDD?

  38. What is Clean Architecture?

  39. What is Repository Pattern?

  40. What is Unit of Work?

  41. What is Factory Pattern?

  42. What is Strategy Pattern?

  43. What is Mediator Pattern?

  44. What is Event-Driven Architecture?

  45. What is eventual consistency?

  46. What is idempotency?

  47. What is circuit breaker?

  48. What is distributed caching?

  49. What is Docker?

  50. What is Kubernetes?


Part 26 – Most Important Lead-Level Questions

I would give extra preparation priority to these:

Architecture

  • Design a scalable .NET application.

  • Monolith vs Microservices.

  • Modular Monolith vs Microservices.

  • Clean Architecture.

  • DDD.

  • CQRS.

  • Event-driven architecture.

  • Distributed transactions.

  • Eventual consistency.

Performance

  • How do you optimize a slow API?

  • How do you optimize EF Core?

  • How do you optimize SQL?

  • How do you implement caching?

  • How do you handle millions of API requests?

  • How do you identify production bottlenecks?

Security

  • JWT authentication.

  • OAuth 2.0.

  • OpenID Connect.

  • Role vs policy authorization.

  • API security.

  • Secret management.

  • OWASP API security concepts.

Azure

  • App Service.

  • Azure Functions.

  • Service Bus.

  • Azure SQL.

  • Key Vault.

  • API Management.

  • Application Insights.

  • Azure Monitor.

  • AKS.

  • CI/CD.

Leadership

  • Handling conflicts.

  • Mentoring developers.

  • Code review.

  • Architecture decisions.

  • Technical debt.

  • Deadlines.

  • Production incidents.

  • Team performance.

  • Communicating with business stakeholders.


Part 27 – Your 60-Second Introduction

For this specific role, your introduction should be Lead-oriented rather than purely developer-oriented.

A good structure is:

Current Role
     ↓
Years of Experience
     ↓
.NET / C# Expertise
     ↓
ASP.NET Core / REST API
     ↓
SQL / EF Core
     ↓
Microservices / Architecture
     ↓
Azure / DevOps
     ↓
Leadership / Mentoring
     ↓
Recent Project

For example, you could say:

"I am a Full Stack .NET professional with strong experience in designing and developing enterprise applications using C#, ASP.NET Core, REST APIs, SQL Server and modern frontend technologies. My experience includes developing scalable backend systems, designing APIs, applying SOLID principles and design patterns, and working with architectures such as Clean Architecture, CQRS and Microservices. I have also worked with Azure services, CI/CD, Docker and distributed systems. In my Lead responsibilities, I focus not only on development but also on architecture decisions, code reviews, mentoring developers, performance optimization, production support and collaboration with business and cross-functional teams. I am particularly interested in building scalable, maintainable and high-performance solutions while helping the team follow good engineering practices."


Recommended Interview Preparation Path

For this particular job description, I recommend preparing in this order:

                     .NET LEAD
                        │
        ┌───────────────┼────────────────┐
        ↓               ↓                ↓
       C#           ASP.NET Core       SQL Server
        │               │                │
        ↓               ↓                ↓
    SOLID/Patterns    REST APIs       EF Core
        │               │                │
        └───────────────┼────────────────┘
                        ↓
                   Architecture
                        ↓
            ┌───────────┴───────────┐
            ↓                       ↓
       Microservices             CQRS/DDD
            ↓                       ↓
            └───────────┬───────────┘
                        ↓
                      Azure
                        ↓
                 DevOps / Docker
                        ↓
                System Design
                        ↓
                    Leadership

For a .NET Lead interview, don't prepare only definitions. Be ready to explain why you selected a technology/pattern, what alternatives you considered, the trade-offs, performance implications, failure scenarios, and how you led the team through the implementation.

A Complete 100-question .NET Lead Interview Questions & Answers guide, organized into C# → ASP.NET Core → Web API → SQL → EF Core → Design Patterns → Microservices → Azure → DevOps → System Design → Leadership, with real-world project scenarios and coding questions.

Template Method Design Pattern

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

  1. Introduction

  2. What is the Template Method Design Pattern?

  3. Why Do We Need the Template Method Pattern?

  4. The Problem with Duplicate Algorithms

  5. Real-World Analogy

  6. Template Method Pattern Structure

  7. UML Class Diagram

  8. Components of the Pattern

  9. Template Method vs Strategy

  10. Complete C# Console Application

  11. Data Processing Example

  12. Payment Processing Example

  13. Hook Methods

  14. ASP.NET Core Implementation

  15. Template Method with Dependency Injection

  16. Real-World Enterprise Examples

  17. Template Method vs Factory Method

  18. Template Method vs Strategy

  19. Template Method vs State

  20. Advantages

  21. Disadvantages

  22. Best Practices

  23. Common Mistakes

  24. Unit Testing

  25. Interview Questions

  26. When Should You Use Template Method?

  27. When Should You Avoid It?

  28. Key Takeaways

  29. Conclusion

  30. 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

FeatureTemplate MethodStrategy
Pattern TypeBehavioralBehavioral
Main GoalDefine algorithm skeletonEncapsulate algorithms
Main MechanismInheritanceComposition
Base ClassUsually requiredNot required
Runtime SwitchingLimitedExcellent
Code ReuseHighModerate
CouplingHigherLower
HooksCommonNot typical
Best ForSame workflow, variable stepsCompletely 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

  • IVisitor and Accept()

  • 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

Visitor Design Pattern

Mastering Design Patterns in C# and ASP.NET Core

Part 4.11 – Visitor Design Pattern

Series: Design Patterns in C# and ASP.NET Core
Pattern Category: Behavioral Design Pattern
Difficulty: ⭐⭐⭐⭐☆ (Advanced)
Prerequisites: C#, OOP, Interfaces, Polymorphism, Inheritance, SOLID Principles, Dependency Injection


Table of Contents

  1. Introduction

  2. What is the Visitor Design Pattern?

  3. Why Do We Need the Visitor Pattern?

  4. The Problem the Visitor Pattern Solves

  5. Real-World Analogy

  6. Visitor Pattern Structure

  7. UML Class Diagram

  8. Components of the Visitor Pattern

  9. The IVisitor Interface

  10. The Accept() Method

  11. Understanding Double Dispatch

  12. Complete C# Console Application

  13. Document Processing Example

  14. Shopping Cart Example

  15. Tax Calculation Example

  16. ASP.NET Core Implementation

  17. Visitor with Dependency Injection

  18. Real-World Enterprise Examples

  19. Visitor vs Strategy

  20. Visitor vs Composite

  21. Visitor vs Interpreter

  22. Visitor vs Decorator

  23. Advantages

  24. Disadvantages

  25. Best Practices

  26. Common Mistakes

  27. Unit Testing

  28. Interview Questions

  29. When Should You Use Visitor?

  30. When Should You Avoid Visitor?

  31. Key Takeaways

  32. Conclusion

  33. Coming Up Next


1. Introduction

Imagine an application that contains many different types of objects:

Customer
Order
Invoice
Product
Employee
Department

Over time, we may need to perform many different operations on these objects:

Calculate Tax
Generate Report
Export to XML
Export to JSON
Validate
Audit
Calculate Discount
Generate Statistics

One approach is to keep adding methods to every domain class.

For example:

public class Product
{
    public void CalculateTax()
    {
    }

    public void GenerateReport()
    {
    }

    public void ExportToXml()
    {
    }

    public void ExportToJson()
    {
    }
}

This becomes difficult to maintain.

The classes gradually become responsible for too many unrelated operations.

The Visitor Design Pattern provides another approach.

Instead of putting every operation inside the domain classes, we can move operations into separate Visitor classes.


2. What is the Visitor Design Pattern?

The Visitor Design Pattern allows us to define new operations on a group of related object types without modifying the classes of those objects.

The basic idea is:

Object Structure
      |
      ↓
Accept Visitor
      |
      ↓
Visitor performs operation

For example:

              Document
                 |
       +---------+---------+
       |         |         |
       ↓         ↓         ↓
     PDF       Word       Excel
       |         |         |
       +---------+---------+
                 |
                 ↓
              Visitor
                 |
       +---------+---------+
       |         |         |
       ↓         ↓         ↓
    Export     Validate    Audit

The object structure remains stable while new operations can be added through new visitors.


3. Why Do We Need the Visitor Pattern?

Consider a document system.

We have:

PdfDocument
WordDocument
ExcelDocument

Initially we need:

Export

Later the requirements change.

We also need:

Validate
Calculate Size
Generate Report
Audit
Compress

Without Visitor, we might repeatedly modify every document class.

For example:

public class PdfDocument
{
    public void Export()
    {
    }

    public void Validate()
    {
    }

    public void CalculateSize()
    {
    }

    public void Audit()
    {
    }
}

Then we do the same for:

WordDocument
ExcelDocument
PowerPointDocument

This creates a maintenance problem.

The Visitor Pattern moves these operations into separate classes.


4. The Problem the Visitor Pattern Solves

Suppose we have:

Element A
Element B
Element C

and need several operations:

Operation 1
Operation 2
Operation 3

Without Visitor:

Element A → Operation 1
Element A → Operation 2
Element A → Operation 3

Element B → Operation 1
Element B → Operation 2
Element B → Operation 3

Element C → Operation 1
Element C → Operation 2
Element C → Operation 3

The classes become large.

With Visitor:

Element A
Element B
Element C
      |
      ↓
Accept(visitor)
      |
      ↓
Visitor
      |
      +── Operation 1
      +── Operation 2
      +── Operation 3

Operations become separate objects.


5. Real-World Analogy

Imagine a hospital with different types of patients:

Child
Adult
Senior

Different specialists visit these patients:

Doctor
Insurance Agent
Nutritionist
Auditor

The patient types remain the same.

But different visitors perform different operations.

For example:

Doctor → Examine Patient
Insurance Agent → Calculate Insurance
Nutritionist → Create Diet Plan
Auditor → Review Records

The patient doesn't need to contain every possible operation.

The visitor performs the operation.

This is the basic idea behind the Visitor Pattern.


6. Visitor Pattern Structure

The pattern typically contains:

Visitor
Concrete Visitor
Element
Concrete Element
Object Structure

The relationship looks like:

             +--------------------+
             |      Visitor       |
             +--------------------+
             | Visit(ElementA)    |
             | Visit(ElementB)    |
             +---------+----------+
                       |
             +---------+----------+
             |                    |
             ↓                    ↓
      ConcreteVisitorA     ConcreteVisitorB


             +--------------------+
             |      Element       |
             +--------------------+
             | Accept(visitor)    |
             +---------+----------+
                       |
             +---------+----------+
             |                    |
             ↓                    ↓
        ElementA             ElementB

7. UML Class Diagram

                 +-------------------------+
                 |       IVisitor          |
                 +-------------------------+
                 | + Visit(Product)        |
                 | + Visit(Service)        |
                 | + Visit(Order)          |
                 +------------+------------+
                              |
                 +------------+------------+
                 |                         |
                 ↓                         ↓
       +------------------+       +------------------+
       | TaxVisitor       |       | ReportVisitor    |
       +------------------+       +------------------+
       | Visit(Product)   |       | Visit(Product)   |
       | Visit(Service)   |       | Visit(Service)   |
       | Visit(Order)     |       | Visit(Order)     |
       +------------------+       +------------------+


                 +-------------------------+
                 |       IElement          |
                 +-------------------------+
                 | + Accept(IVisitor)      |
                 +------------+------------+
                              |
                 +------------+------------+
                 |                         |
                 ↓                         ↓
          +-------------+           +-------------+
          | Product     |           | Service     |
          +-------------+           +-------------+
          | Accept()    |           | Accept()    |
          +-------------+           +-------------+

8. Components of the Visitor Pattern

8.1 Visitor

Defines operations for each element type.

public interface IVisitor
{
    void Visit(Product product);

    void Visit(Service service);
}

8.2 Concrete Visitor

Implements a specific operation.

For example:

TaxVisitor
ReportVisitor
AuditVisitor
ExportVisitor

8.3 Element

Defines:

void Accept(IVisitor visitor);

8.4 Concrete Element

Examples:

Product
Service
Order
Invoice

Each element implements Accept().


9. The IVisitor Interface

Let's create a simple example.

public interface IVisitor
{
    void Visit(Product product);

    void Visit(Service service);
}

This means the visitor knows how to process:

Product
Service

10. The IElement Interface

public interface IElement
{
    void Accept(IVisitor visitor);
}

Each element accepts a visitor.


11. Product

public class Product : IElement
{
    public string Name { get; set; }

    public decimal Price { get; set; }

    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
}

Notice:

visitor.Visit(this);

The current object is passed to the visitor.


12. Service

public class Service : IElement
{
    public string Name { get; set; }

    public decimal Price { get; set; }

    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
}

Again:

visitor.Visit(this);

The visitor gets the concrete type.


13. Understanding Double Dispatch

This is one of the most important concepts in the Visitor Pattern.

Suppose we have:

element.Accept(visitor);

At first glance, it looks like one method call.

But two types determine the final behavior:

Element Type
+
Visitor Type

For example:

product.Accept(taxVisitor);

The Product class executes:

visitor.Visit(this);

Because this is a Product, the compiler selects:

Visit(Product product)

So the operation depends on both:

Product
+
TaxVisitor

This technique is known as Double Dispatch.


14. Complete C# Console Application

Let's create a complete example.

Step 1 – Visitor Interface

public interface IVisitor
{
    void Visit(Product product);

    void Visit(Service service);
}

Step 2 – Element Interface

public interface IElement
{
    void Accept(IVisitor visitor);
}

Step 3 – Product

public class Product : IElement
{
    public string Name { get; }

    public decimal Price { get; }

    public Product(
        string name,
        decimal price)
    {
        Name = name;
        Price = price;
    }

    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
}

Step 4 – Service

public class Service : IElement
{
    public string Name { get; }

    public decimal Price { get; }

    public Service(
        string name,
        decimal price)
    {
        Name = name;
        Price = price;
    }

    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
}

15. Tax Visitor

public class TaxVisitor : IVisitor
{
    public void Visit(Product product)
    {
        var tax = product.Price * 0.10m;

        Console.WriteLine(
            $"Product: {product.Name}");

        Console.WriteLine(
            $"Price: {product.Price:C}");

        Console.WriteLine(
            $"Tax: {tax:C}");
    }

    public void Visit(Service service)
    {
        var tax = service.Price * 0.15m;

        Console.WriteLine(
            $"Service: {service.Name}");

        Console.WriteLine(
            $"Price: {service.Price:C}");

        Console.WriteLine(
            $"Tax: {tax:C}");
    }
}

Here, the tax calculation is outside the domain classes.


16. Report Visitor

We can add another operation without modifying Product or Service.

public class ReportVisitor : IVisitor
{
    public void Visit(Product product)
    {
        Console.WriteLine(
            $"Product Report: {product.Name} - {product.Price:C}");
    }

    public void Visit(Service service)
    {
        Console.WriteLine(
            $"Service Report: {service.Name} - {service.Price:C}");
    }
}

This demonstrates one of the primary benefits of Visitor:

New operations can be introduced without modifying the element classes.


17. Program.cs

var elements = new List<IElement>
{
    new Product("Laptop", 1200m),
    new Product("Monitor", 400m),
    new Service("Consulting", 800m)
};

IVisitor taxVisitor = new TaxVisitor();

foreach (var element in elements)
{
    element.Accept(taxVisitor);
}

Console.WriteLine();

IVisitor reportVisitor =
    new ReportVisitor();

foreach (var element in elements)
{
    element.Accept(reportVisitor);
}

The same objects can be processed by different visitors.


18. Execution Flow

When this code executes:

element.Accept(taxVisitor);

the flow is:

Product
   |
   ↓
Accept(taxVisitor)
   |
   ↓
taxVisitor.Visit(this)
   |
   ↓
Visit(Product)
   |
   ↓
Calculate Product Tax

For a service:

Service
   |
   ↓
Accept(taxVisitor)
   |
   ↓
taxVisitor.Visit(this)
   |
   ↓
Visit(Service)
   |
   ↓
Calculate Service Tax

This is the essence of double dispatch.


19. Object Structure

A visitor is often used with a collection or object structure.

For example:

public class ShoppingCart
{
    private readonly List<IElement> _items =
        new();

    public void Add(IElement item)
    {
        _items.Add(item);
    }

    public void Accept(IVisitor visitor)
    {
        foreach (var item in _items)
        {
            item.Accept(visitor);
        }
    }
}

Usage:

var cart = new ShoppingCart();

cart.Add(
    new Product("Laptop", 1200m));

cart.Add(
    new Service("Consulting", 800m));

cart.Accept(
    new TaxVisitor());

Now the object structure controls traversal.


20. Shopping Cart Example

Imagine an e-commerce cart containing:

Physical Product
Digital Product
Subscription
Service

We may need operations such as:

Calculate Tax
Calculate Discount
Generate Invoice
Calculate Shipping
Generate Analytics

Instead of adding all these methods to every item, we can create:

TaxVisitor
DiscountVisitor
InvoiceVisitor
ShippingVisitor
AnalyticsVisitor

The architecture becomes:

                Shopping Cart
                     |
       +-------------+-------------+
       |             |             |
       ↓             ↓             ↓
    Product       Service      Subscription
       |             |             |
       +-------------+-------------+
                     |
                  Accept()
                     |
                     ↓
                  Visitor

21. Tax Calculation Example

A tax visitor could implement:

public class TaxCalculationVisitor : IVisitor
{
    public void Visit(Product product)
    {
        Console.WriteLine(
            $"Calculating product tax for {product.Name}");
    }

    public void Visit(Service service)
    {
        Console.WriteLine(
            $"Calculating service tax for {service.Name}");
    }
}

The important design decision is that tax calculation does not belong directly to the product or service entity.

This keeps the domain objects focused on their own responsibilities.


22. Document Processing Example

Consider a document processing system:

PdfDocument
WordDocument
ExcelDocument

Operations could include:

Export
Validate
Compress
Audit
Generate Preview

We could create:

ExportVisitor
ValidationVisitor
CompressionVisitor
AuditVisitor
PreviewVisitor

The structure becomes:

Document
   |
   +-- PDF
   +-- Word
   +-- Excel
        |
        ↓
      Accept()
        |
        ↓
      Visitor

23. ASP.NET Core Implementation

Let's create an ASP.NET Core example.

Suppose an e-commerce API has:

Product
Service
Subscription

and we want to calculate different business operations.


24. Element Interface

public interface ICartItem
{
    void Accept(ICartVisitor visitor);
}

25. Visitor Interface

public interface ICartVisitor
{
    void Visit(Product product);

    void Visit(Service service);

    void Visit(Subscription subscription);
}

26. Product

public class Product : ICartItem
{
    public string Name { get; }

    public decimal Price { get; }

    public Product(
        string name,
        decimal price)
    {
        Name = name;
        Price = price;
    }

    public void Accept(ICartVisitor visitor)
    {
        visitor.Visit(this);
    }
}

27. Service

public class Service : ICartItem
{
    public string Name { get; }

    public decimal Price { get; }

    public Service(
        string name,
        decimal price)
    {
        Name = name;
        Price = price;
    }

    public void Accept(ICartVisitor visitor)
    {
        visitor.Visit(this);
    }
}

28. Subscription

public class Subscription : ICartItem
{
    public string Name { get; }

    public decimal MonthlyPrice { get; }

    public Subscription(
        string name,
        decimal monthlyPrice)
    {
        Name = name;
        MonthlyPrice = monthlyPrice;
    }

    public void Accept(ICartVisitor visitor)
    {
        visitor.Visit(this);
    }
}

29. Pricing Visitor

public class PricingVisitor : ICartVisitor
{
    public decimal Total { get; private set; }

    public void Visit(Product product)
    {
        Total += product.Price;
    }

    public void Visit(Service service)
    {
        Total += service.Price;
    }

    public void Visit(Subscription subscription)
    {
        Total += subscription.MonthlyPrice;
    }
}

30. Registering with Dependency Injection

In ASP.NET Core:

builder.Services.AddScoped<PricingVisitor>();

We can inject the visitor into an application service.

public class CartService
{
    private readonly PricingVisitor _pricingVisitor;

    public CartService(
        PricingVisitor pricingVisitor)
    {
        _pricingVisitor = pricingVisitor;
    }

    public decimal CalculateTotal(
        IEnumerable<ICartItem> items)
    {
        foreach (var item in items)
        {
            item.Accept(_pricingVisitor);
        }

        return _pricingVisitor.Total;
    }
}

In a larger application, it is often preferable for visitors to be stateless or to return results instead of holding mutable request state.


31. Controller Example

[ApiController]
[Route("api/cart")]
public class CartController : ControllerBase
{
    private readonly CartService _cartService;

    public CartController(
        CartService cartService)
    {
        _cartService = cartService;
    }

    [HttpPost("calculate")]
    public IActionResult Calculate()
    {
        var items = new List<ICartItem>
        {
            new Product("Laptop", 1200m),
            new Service("Installation", 100m),
            new Subscription("Cloud Storage", 25m)
        };

        var total =
            _cartService.CalculateTotal(items);

        return Ok(new
        {
            Total = total
        });
    }
}

32. Visitor with Dependency Injection

Visitor Pattern and Dependency Injection work well together.

For example, a visitor may depend on:

ILogger
Repository
Tax Service
Configuration
External API Client
Audit Service

Example:

public class AuditVisitor : ICartVisitor
{
    private readonly ILogger<AuditVisitor> _logger;

    public AuditVisitor(
        ILogger<AuditVisitor> logger)
    {
        _logger = logger;
    }

    public void Visit(Product product)
    {
        _logger.LogInformation(
            "Product visited: {Name}",
            product.Name);
    }

    public void Visit(Service service)
    {
        _logger.LogInformation(
            "Service visited: {Name}",
            service.Name);
    }

    public void Visit(Subscription subscription)
    {
        _logger.LogInformation(
            "Subscription visited: {Name}",
            subscription.Name);
    }
}

This makes the Visitor suitable for enterprise scenarios where operations need external services.


33. Real-World Enterprise Example – Tax Calculation

Tax calculation can become complicated when different elements have different rules.

For example:

Product
Service
Subscription
Digital Product
Physical Product

A visitor can encapsulate the tax rules:

TaxVisitor
     |
     +-- Product Tax
     +-- Service Tax
     +-- Subscription Tax
     +-- Digital Product Tax

This avoids spreading tax calculation logic throughout the domain model.


34. Real-World Enterprise Example – Reporting

Suppose an application contains:

Customer
Order
Invoice
Payment

We need:

Financial Report
Audit Report
Management Report
Export Report

Visitors can represent these operations:

FinancialReportVisitor
AuditVisitor
ManagementReportVisitor
ExportVisitor

The domain model stays relatively stable.


35. Real-World Enterprise Example – AST / Compiler

One of the classic uses of Visitor is processing an Abstract Syntax Tree (AST).

For example:

Expression
   |
   +-- Addition
   +-- Subtraction
   +-- Multiplication
   +-- Number

Different visitors can perform:

Evaluate
Generate Code
Validate
Pretty Print
Optimize

For example:

Expression Tree
      |
      ↓
EvaluationVisitor
      |
      ↓
Calculate Result

Another visitor can process the same tree:

Expression Tree
      |
      ↓
CodeGenerationVisitor
      |
      ↓
Generate Code

This is one of the strongest examples of where Visitor can be useful.


36. Visitor vs Strategy

Both are behavioral patterns, but their goals differ.

Strategy

Strategy encapsulates an algorithm.

PaymentService
      |
      ↓
PaymentStrategy
      |
 +----+----+
 |    |    |
Card PayPal Bank

You choose which algorithm to execute.


Visitor

Visitor separates operations from a stable object structure.

Object Structure
      |
      ↓
Accept Visitor
      |
      ↓
Operation

Simple rule:

Strategy changes how something is done. Visitor adds operations to an object structure.


37. Visitor vs Composite

Composite represents a tree-like object structure:

Folder
 |
 +-- File
 +-- Folder
      |
      +-- File

Visitor can then traverse that structure and perform operations:

FileSizeVisitor
SearchVisitor
SecurityAuditVisitor

In fact, Visitor and Composite are often used together.


38. Visitor vs Interpreter

Interpreter is designed around interpreting a language or grammar.

For example:

Expression
   |
   +-- Number
   +-- Addition
   +-- Subtraction

Visitor can be used to perform operations on such expression trees.

A common relationship is:

Interpreter
    +
Visitor

where Visitor processes the resulting expression tree.


39. Visitor vs Decorator

Decorator adds behavior around an object:

Object
  ↓
Decorator
  ↓
Decorator
  ↓
Concrete Object

Visitor does not wrap the object.

Instead:

Object
  ↓
Accept(visitor)
  ↓
Visitor operation

Simple distinction

Decorator
→ Add behavior to an object

Visitor
→ Add operations to an object structure

40. Advantages

1. Adds New Operations Easily

A new visitor can be introduced without changing existing element classes.


2. Separates Operations from Data

Domain objects don't need to contain every possible operation.


3. Supports Single Responsibility

Business operations can be separated into dedicated visitor classes.


4. Useful for Stable Object Structures

If element types don't change frequently but operations change often, Visitor can be very effective.


5. Works Well with Composite

Visitor is particularly useful when processing hierarchical structures.


6. Centralizes Related Operations

For example:

TaxVisitor

can contain all tax-related logic.


41. Disadvantages

1. Adding New Element Types Is Difficult

Suppose we add:

Subscription

Every visitor interface may need:

void Visit(Subscription subscription);

and every concrete visitor must implement it.

This is the biggest trade-off.


2. Can Increase Complexity

The pattern requires:

Visitor
Concrete Visitor
Element
Concrete Element
Accept()

For simple applications, this may be unnecessary.


3. Tight Coupling Between Visitor and Elements

The visitor interface must know the concrete element types.


4. Double Dispatch Can Be Difficult for Beginners

The Accept()Visit() flow takes time to understand.


5. Not Ideal for Frequently Changing Object Structures

If new element types are added regularly, maintaining all visitors can become expensive.


42. Best Practices

1. Use Visitor When the Object Structure Is Stable

This is the most important guideline.


2. Use Visitor When Operations Change Frequently

For example:

Stable:
Product
Service
Subscription

Changing:
Tax
Audit
Reporting
Export

Visitor is a strong candidate.


3. Keep Visitors Focused

Prefer:

TaxVisitor
AuditVisitor
ExportVisitor

rather than:

EverythingVisitor

4. Avoid Stateful Visitors When Possible

Prefer visitors that return results or accumulate results in a controlled way.


5. Use Interfaces

For example:

IVisitor
IElement

This improves testability and extensibility.


6. Don't Force Visitor into Simple Applications

If there are only two classes and one operation, a normal method may be better.


7. Combine Visitor with Composite When Appropriate

For hierarchical structures, the combination can be powerful.


43. Common Mistakes

Mistake 1 – Using Visitor When Element Types Change Frequently

If new element types are constantly added, every visitor needs modification.


Mistake 2 – Creating Huge Visitors

A visitor containing hundreds of unrelated operations becomes difficult to maintain.


Mistake 3 – Ignoring Double Dispatch

Developers sometimes misunderstand why:

visitor.Visit(this);

is necessary.

It allows the concrete element type to select the correct overloaded visitor method.


Mistake 4 – Mixing Too Many Responsibilities

Each visitor should represent a meaningful operation.


Mistake 5 – Using Visitor for Simple Business Logic

Don't introduce Visitor just because it is a design pattern.

Use it when the problem actually matches the pattern.


44. Unit Testing Visitor

Visitors are usually straightforward to unit test.

Example:

[Fact]
public void TaxVisitor_ShouldCalculate_ProductTax()
{
    var product =
        new Product("Laptop", 1000m);

    var visitor =
        new TaxVisitor();

    product.Accept(visitor);

    // Assert expected tax behavior.
}

For enterprise applications, visitors can receive mocked dependencies:

TaxVisitor
   |
   +-- TaxService Mock
   +-- Logger Mock
   +-- Repository Mock

This allows isolated testing.


45. Interview Questions

Beginner

1. What is the Visitor Design Pattern?

It allows new operations to be added to an object structure without modifying the classes of the objects being operated on.


2. Which category does Visitor belong to?

Behavioral Design Pattern.


3. What are the main components?

Typically:

Visitor
Concrete Visitor
Element
Concrete Element
Object Structure

4. What is Accept()?

It is the method through which an element accepts a visitor.

void Accept(IVisitor visitor);

5. What is Visit()?

It is the visitor operation for a particular concrete element.


Intermediate

6. What is Double Dispatch?

Double Dispatch is a technique where the final method selected depends on both the runtime element and visitor types.


7. Why is visitor.Visit(this) used?

It passes the concrete element to the visitor, allowing the appropriate overloaded Visit() method to be selected.


8. What is the biggest advantage of Visitor?

Adding new operations is relatively easy without modifying existing element classes.


9. What is the biggest disadvantage?

Adding a new element type requires changes to the visitor interface and usually all concrete visitors.


10. Visitor vs Strategy?

Strategy encapsulates interchangeable algorithms.

Visitor separates operations from a relatively stable object structure.


11. Visitor vs Decorator?

Decorator wraps objects to add behavior.

Visitor performs operations on objects without wrapping them.


12. Can Visitor work with Composite?

Yes. Visitor is frequently used with Composite to traverse and process tree structures.


Advanced

13. When should you use Visitor?

When:

Object types are relatively stable
AND
Operations change frequently

14. When should you avoid Visitor?

When new element types are added frequently or the object structure is unstable.


15. Can Visitor use Dependency Injection?

Yes. Visitors can receive repositories, services, loggers, APIs, and other dependencies through ASP.NET Core DI.


16. Why is Visitor considered an advanced pattern?

Because it involves:

Polymorphism
Overloading
Double Dispatch
Object Structures
Separation of Operations

17. Can Visitor return a value?

Yes.

Instead of:

void Visit(Product product)

you can design visitors around results, for example:

decimal Visit(Product product);

or use a result object.

The appropriate approach depends on the operation.


18. Is Visitor always better than adding methods to the classes?

No.

Visitor is useful only when its trade-offs match the application's structure.


46. When Should You Use Visitor?

Visitor is a good choice when:

  • The object structure is relatively stable.

  • Many operations need to be performed on those objects.

  • New operations are added frequently.

  • You want to separate business operations from domain objects.

  • You need to process tree structures.

  • You need different algorithms over the same object hierarchy.

Typical examples:

AST Processing
Document Processing
Tax Calculation
Reporting
Auditing
Code Generation
File System Traversal
Shopping Cart Processing
Analytics
Validation

47. When Should You Avoid Visitor?

Avoid Visitor when:

  • Element types change frequently.

  • The object model is very small.

  • Only one or two operations exist.

  • The pattern would introduce unnecessary complexity.

  • Simple polymorphism can solve the problem.

  • Strategy or another pattern is a better fit.


48. Visitor Pattern in Enterprise Architecture

A possible enterprise architecture is:

                    ASP.NET Core API
                           |
                           ↓
                    Application Layer
                           |
                           ↓
                     Object Structure
                           |
             +-------------+-------------+
             |             |             |
             ↓             ↓             ↓
          Product       Service      Subscription
             |             |             |
             +-------------+-------------+
                           |
                        Accept()
                           |
                           ↓
                       Visitor
                           |
          +----------------+----------------+
          |                |                |
          ↓                ↓                ↓
       TaxVisitor      AuditVisitor    ReportVisitor

This can help keep business operations separate from domain objects.


49. Visitor + Composite

A powerful combination is:

Composite
   +
Visitor

For example, a company hierarchy:

Company
 |
 +-- Department
 |     |
 |     +-- Employee
 |     +-- Employee
 |
 +-- Department
       |
       +-- Employee
       +-- Employee

Visitors can perform:

SalaryVisitor
AuditVisitor
ReportVisitor
PerformanceVisitor

The Composite handles the hierarchy.

The Visitor handles the operation.


50. Visitor + Dependency Injection + ASP.NET Core

A modern .NET application can combine:

ASP.NET Core
      ↓
Dependency Injection
      ↓
Application Service
      ↓
Visitor
      ↓
Domain Objects

For example:

POST /api/orders/calculate-tax
                |
                ↓
         OrderController
                |
                ↓
          OrderService
                |
                ↓
           TaxVisitor
                |
        +-------+-------+
        |       |       |
        ↓       ↓       ↓
     Product Service Subscription

This can be particularly useful when visitor operations require infrastructure services.


51. Simple Memory Trick

Remember Visitor using this phrase:

"Keep the objects stable, move the operations outside."

For example:

Stable:
Product
Service
Subscription

Operations:

Tax
Audit
Report
Export

Move operations into visitors:

TaxVisitor
AuditVisitor
ReportVisitor
ExportVisitor

52. Visitor vs Other Behavioral Patterns

PatternMain Purpose
Chain of ResponsibilityPass request through handlers
CommandEncapsulate a request as an object
InterpreterInterpret a language/grammar
IteratorTraverse a collection
MediatorCentralize communication
MementoCapture and restore state
ObserverNotify dependent objects
StateChange behavior based on state
StrategyEncapsulate interchangeable algorithms
Template MethodDefine algorithm skeleton
VisitorAdd operations to object structures

53. Complete Visitor Flow

The complete flow can be remembered as:

                    Object Structure
                           |
                           ↓
                    Concrete Element
                           |
                           ↓
                    Accept(visitor)
                           |
                           ↓
                  visitor.Visit(this)
                           |
                           ↓
                 Concrete Visitor
                           |
                           ↓
                  Perform Operation

For example:

Product
   |
   ↓
Accept(TaxVisitor)
   |
   ↓
TaxVisitor.Visit(Product)
   |
   ↓
Calculate Product Tax

54. Key Takeaways

Remember these important points:

1. Visitor is a Behavioral Design Pattern

It focuses on operations performed across object structures.

2. Visitor separates operations from objects

Instead of adding every operation to domain classes, operations can be implemented in visitors.

3. Accept() is critical

public void Accept(IVisitor visitor)
{
    visitor.Visit(this);
}

4. Double Dispatch is central to the pattern

The selected operation depends on both the visitor and concrete element type.

5. Visitor is excellent when the object structure is stable

For example:

Product
Service
Subscription

while operations frequently change:

Tax
Audit
Report
Export

6. Visitor makes adding operations easier

A new visitor can represent a new operation.

7. Adding element types is expensive

If we add:

NewElement

the visitor interface and all concrete visitors may need modification.

8. Visitor and Composite work well together

Visitor can traverse and operate on hierarchical structures.


55. Conclusion

The Visitor Design Pattern provides a powerful way to separate operations from the objects on which those operations operate.

The fundamental idea is:

Object Structure
      |
      ↓
Accept Visitor
      |
      ↓
Visitor performs operation

Instead of continuously adding business operations to domain classes:

Product
   ├── Tax
   ├── Audit
   ├── Reporting
   ├── Export
   └── Validation

we can separate them:

Product
Service
Subscription
      |
      ↓
    Accept()
      |
      +── TaxVisitor
      +── AuditVisitor
      +── ReportVisitor
      +── ExportVisitor

This can make the design cleaner when the object structure is stable but operations change frequently.

However, Visitor is not a universal solution. If your application frequently adds new element types, the pattern can become expensive because every visitor must be updated.

The key rule to remember is:

Use Visitor when you have a stable object structure and need to frequently add new operations across that structure.


🎯 Behavioral Design Patterns – Complete!

With Part 4.11, we have now covered the major Behavioral Design Patterns in this series:

Part 4.1  → Chain of Responsibility
Part 4.2  → Command
Part 4.3  → Interpreter
Part 4.4  → Iterator
Part 4.5  → Mediator
Part 4.6  → Memento
Part 4.7  → Observer
Part 4.8  → State
Part 4.9  → Strategy
Part 4.10 → Template Method
Part 4.11 → Visitor

You have now completed:

                 DESIGN PATTERNS
                       |
          +------------+------------+
          |            |            |
          ↓            ↓            ↓
     Creational   Structural   Behavioral
          |            |            |
        5 Patterns   7 Patterns  11 Patterns

That's 23 classic GoF Design Patterns covered across the series.


🚀 Coming Up Next

Part 5 – Design Patterns in Real-World .NET Architecture

The next section can bring everything together by showing how these patterns are actually used in enterprise applications.

Possible topics include:

  • Design Patterns in Clean Architecture

  • Design Patterns in Microservices

  • Design Patterns in ASP.NET Core Web APIs

  • Design Patterns with Entity Framework Core

  • Design Patterns with CQRS

  • Design Patterns with MediatR

  • Design Patterns with Azure Services

  • Design Patterns in Event-Driven Architecture

  • Design Patterns in E-Commerce Systems

  • Design Patterns in Banking Applications

  • Combining Multiple Design Patterns

  • Common Design Pattern Anti-Patterns

  • How to Choose the Right Design Pattern

  • Design Patterns Interview Guide

  • Complete Real-World .NET Architecture Case Study

The next section will focus less on learning patterns individually and more on understanding how experienced .NET developers and architects combine multiple patterns to build maintainable, scalable, and enterprise-ready applications.

Don't Copy

Protected by Copyscape Online Plagiarism Checker