Monday, July 13, 2026

Facade Design Pattern

Mastering Design Patterns in C# and ASP.NET Core

Part 3.5 – Facade Design Pattern

Series: Design Patterns in C# and ASP.NET Core
Pattern Category: Structural Design Pattern
Difficulty: ⭐⭐⭐☆☆ (Intermediate)
Prerequisites: OOP Concepts, Classes, Interfaces, Dependency Injection, SOLID Principles


Table of Contents

  1. Introduction

  2. What is the Facade Design Pattern?

  3. Why Do We Need the Facade Pattern?

  4. The Problem with Complex Subsystems

  5. Real-World Analogy

  6. Facade Pattern Structure

  7. UML Class Diagram

  8. Components of the Facade Pattern

  9. Complete C# Console Application

  10. ASP.NET Core Implementation

  11. Real-World Banking Example

  12. E-Commerce Checkout Example

  13. Advantages

  14. Disadvantages

  15. Best Practices

  16. Common Mistakes

  17. Facade vs Adapter vs Mediator

  18. Real-World Uses in .NET

  19. Interview Questions

  20. Summary


Introduction

As enterprise applications grow, they often become collections of many interconnected components and services. A single business operation may require interactions with databases, external APIs, payment gateways, logging services, notification systems, authentication providers, and caching mechanisms.

For example, consider an e-commerce checkout process. Completing a single order may involve:

  • Validating the shopping cart

  • Checking inventory

  • Calculating discounts

  • Processing payment

  • Creating the order

  • Updating stock

  • Sending confirmation emails

  • Logging the transaction

  • Generating an invoice

If the client application has to communicate directly with every subsystem, the code quickly becomes difficult to understand, maintain, and extend.

This is where the Facade Design Pattern becomes invaluable. It provides a single, simplified interface that hides the complexity of the underlying subsystems.


What is the Facade Design Pattern?

Definition

The Facade Design Pattern is a Structural Design Pattern that provides a single unified interface to a set of interfaces in a complex subsystem.

Instead of exposing all subsystem components to the client, the Facade encapsulates their interactions and offers a simple API.

In simple terms:

Facade hides complexity behind a simple interface.


Why Do We Need the Facade Pattern?

Imagine a banking application where a customer wants to transfer money.

Without a Facade, the application might need to interact with:

  • Account Validation Service

  • Balance Service

  • Fraud Detection Service

  • Transaction Service

  • Notification Service

  • Audit Logging Service

The client would have to coordinate all these services manually.

With a Facade, the client simply calls:

bankFacade.TransferMoney(fromAccount, toAccount, amount);

The Facade internally coordinates all subsystem operations, making the client code much simpler and easier to maintain.


The Problem with Complex Subsystems

Consider an online shopping application.

Without the Facade Pattern:

Customer

↓

Inventory Service

↓

Payment Service

↓

Shipping Service

↓

Invoice Service

↓

Notification Service

↓

Logging Service

The client must understand how each subsystem works and in what order to call them.

Problems include:

  • Tight coupling

  • Complex client code

  • Difficult maintenance

  • High risk of errors

  • Repeated orchestration logic

The Facade Pattern centralizes this orchestration.


Real-World Analogy

Imagine visiting a hospital.

Instead of contacting:

  • Reception

  • Billing

  • Doctor

  • Laboratory

  • Pharmacy

individually, you visit the Reception Desk.

The receptionist coordinates everything on your behalf.

The receptionist acts as the Facade.

Similarly, in software, the Facade provides one point of entry into a complex system.


Facade Pattern Structure

The Facade Pattern consists of:

  • Client

  • Facade

  • Multiple Subsystem Classes

The client communicates only with the Facade, while the Facade coordinates the subsystem components.


UML Class Diagram

                    +-------------------+
                    |      Client       |
                    +-------------------+
                              |
                              |
                              V
                    +-------------------+
                    |   BankingFacade   |
                    +-------------------+
                    | + TransferMoney() |
                    +---------+---------+
                              |
        +---------------------+----------------------+
        |                     |                      |
        V                     V                      V
+----------------+   +----------------+   +----------------+
| AccountService |   | PaymentService |   | Notification   |
+----------------+   +----------------+   +----------------+
| Validate()     |   | Transfer()     |   | SendSMS()      |
+----------------+   +----------------+   +----------------+

Components of the Facade Pattern

Client

The client interacts only with the Facade.


Facade

Coordinates the subsystem classes.

public class BankingFacade
{
    private readonly AccountService accountService;
    private readonly PaymentService paymentService;
    private readonly NotificationService notificationService;

    public BankingFacade()
    {
        accountService = new AccountService();
        paymentService = new PaymentService();
        notificationService = new NotificationService();
    }

    public void TransferMoney(string from, string to, decimal amount)
    {
        accountService.Validate(from);

        paymentService.Transfer(from, to, amount);

        notificationService.SendNotification("Transfer Successful");
    }
}

Subsystem Classes

public class AccountService
{
    public void Validate(string account)
    {
        Console.WriteLine("Account Validated");
    }
}

public class PaymentService
{
    public void Transfer(string from, string to, decimal amount)
    {
        Console.WriteLine($"Transferred ₹{amount}");
    }
}

public class NotificationService
{
    public void SendNotification(string message)
    {
        Console.WriteLine(message);
    }
}

Complete C# Console Application

class Program
{
    static void Main()
    {
        BankingFacade banking = new BankingFacade();

        banking.TransferMoney(
            "ACC1001",
            "ACC2002",
            5000);
    }
}

Output

Account Validated
Transferred ₹5000
Transfer Successful

The client interacts with only one class while the Facade manages all subsystem operations.


ASP.NET Core Implementation

Suppose an e-commerce application requires several services during checkout.

Services

public interface IInventoryService
{
    void ReserveStock();
}

public interface IPaymentService
{
    void ProcessPayment();
}

public interface IShippingService
{
    void CreateShipment();
}

public interface INotificationService
{
    void SendConfirmation();
}

Facade

public class CheckoutFacade
{
    private readonly IInventoryService inventory;
    private readonly IPaymentService payment;
    private readonly IShippingService shipping;
    private readonly INotificationService notification;

    public CheckoutFacade(
        IInventoryService inventory,
        IPaymentService payment,
        IShippingService shipping,
        INotificationService notification)
    {
        this.inventory = inventory;
        this.payment = payment;
        this.shipping = shipping;
        this.notification = notification;
    }

    public void Checkout()
    {
        inventory.ReserveStock();

        payment.ProcessPayment();

        shipping.CreateShipment();

        notification.SendConfirmation();
    }
}

Dependency Injection

builder.Services.AddScoped<IInventoryService, InventoryService>();
builder.Services.AddScoped<IPaymentService, PaymentService>();
builder.Services.AddScoped<IShippingService, ShippingService>();
builder.Services.AddScoped<INotificationService, NotificationService>();

builder.Services.AddScoped<CheckoutFacade>();

Controller

[ApiController]
[Route("api/orders")]
public class OrderController : ControllerBase
{
    private readonly CheckoutFacade checkout;

    public OrderController(CheckoutFacade checkout)
    {
        this.checkout = checkout;
    }

    [HttpPost]
    public IActionResult PlaceOrder()
    {
        checkout.Checkout();

        return Ok("Order Placed Successfully");
    }
}

The controller remains clean and focused, while the Facade coordinates all business operations.


Real-World Banking Example

A money transfer typically involves:

  • Validate sender account

  • Validate receiver account

  • Check balance

  • Perform fraud detection

  • Debit sender account

  • Credit receiver account

  • Log transaction

  • Send SMS/Email notification

Without a Facade, the client would call each service individually.

With a BankingFacade, all of these operations are executed through a single method:

bankingFacade.TransferMoney();

E-Commerce Checkout Example

A checkout process may involve:

Customer

↓

Shopping Cart Validation

↓

Inventory Check

↓

Discount Calculation

↓

Payment Processing

↓

Invoice Generation

↓

Shipment Creation

↓

Email Notification

↓

Order Completed

The CheckoutFacade hides all this complexity behind one simple API.


Advantages

  • Simplifies interaction with complex systems.

  • Reduces coupling between clients and subsystems.

  • Improves code readability.

  • Promotes separation of concerns.

  • Centralizes orchestration logic.

  • Makes subsystem changes transparent to clients.

  • Easier maintenance and testing.


Disadvantages

  • The Facade can become a "God Object" if too many responsibilities are added.

  • May hide useful subsystem functionality that advanced clients need.

  • Adds an additional abstraction layer.

  • Poorly designed facades can become difficult to maintain.


Best Practices

  • Keep the Facade focused on a single business workflow.

  • Do not implement business rules unrelated to orchestration.

  • Inject subsystem dependencies using Dependency Injection.

  • Avoid exposing subsystem objects directly to clients.

  • Split large facades into smaller, feature-specific facades when necessary.


Common Mistakes

Creating a Huge Facade

Avoid placing unrelated operations in one large facade class. Create separate facades for different business domains.


Putting Business Logic Inside the Facade

The Facade should coordinate services, not replace them.


Ignoring Dependency Injection

Always inject subsystem services rather than creating them manually inside the facade.


Overusing the Pattern

Use a Facade only when subsystem complexity justifies it. For simple systems, it may add unnecessary abstraction.


Facade vs Adapter vs Mediator

FeatureFacadeAdapterMediator
PurposeSimplifies subsystemConverts interfacesCoordinates object interactions
Changes InterfaceNoYesNo
Simplifies UsageYesSometimesNo
Primary GoalHide complexityCompatibilityReduce direct communication

Real-World Uses in .NET

The Facade Pattern is widely used in enterprise .NET applications, including:

  • ASP.NET Core service layers

  • Repository and Unit of Work abstractions

  • E-commerce checkout workflows

  • Banking transaction processing

  • Healthcare appointment systems

  • Travel booking systems

  • Azure service orchestration

  • Microservices API gateways (conceptually similar in providing a unified entry point)


Interview Questions

1. What is the Facade Design Pattern?

It is a structural design pattern that provides a simplified interface to a complex subsystem.


2. When should you use the Facade Pattern?

When clients need to interact with multiple subsystem classes and you want to simplify those interactions.


3. Does the Facade Pattern hide subsystem classes?

Yes. Clients typically interact only with the Facade, while the Facade manages communication with subsystem classes.


4. Which SOLID principles does the Facade Pattern support?

  • Single Responsibility Principle (SRP): The client focuses on business tasks while the Facade manages orchestration.

  • Dependency Inversion Principle (DIP): The Facade can depend on abstractions (interfaces) instead of concrete implementations.


5. What is the difference between Facade and Adapter?

  • Facade simplifies access to a complex subsystem.

  • Adapter converts one interface into another compatible interface.


6. What are common real-world examples of the Facade Pattern?

  • Banking transactions

  • E-commerce checkout

  • Hospital management systems

  • Home theater systems

  • Cloud service orchestration

  • Travel booking platforms


Summary

The Facade Design Pattern is one of the most practical structural patterns for enterprise software development. By providing a unified, easy-to-use interface over complex subsystems, it reduces coupling, simplifies client code, and improves maintainability.

In C# and ASP.NET Core applications, the Facade Pattern is especially useful for orchestrating workflows such as banking transactions, order processing, document generation, and service integrations. When applied correctly, it creates cleaner architectures, improves readability, and makes systems easier to evolve as business requirements grow.


Coming Up Next: Part 3.6 – Flyweight Design Pattern

In the next article, we'll explore the Flyweight Design Pattern, including:

  • What is the Flyweight Pattern?

  • Why memory optimization matters

  • Intrinsic vs. Extrinsic State

  • Object Sharing and Caching

  • UML Class Diagram

  • Complete C# Console Application

  • ASP.NET Core implementation

  • Real-world examples (Text Editors, Game Development, Icon Libraries)

  • Advantages and disadvantages

  • Best practices

  • Common mistakes

  • Interview questions

You'll learn how the Flyweight Pattern minimizes memory usage by sharing common object state, making it ideal for high-performance applications that create and manage a large number of similar objects.

Decorator Design Pattern

 Mastering Design Patterns in C# and ASP.NET Core

Part 3.4 – Decorator Design Pattern

Series: Design Patterns in C# and ASP.NET Core
Pattern Category: Structural Design Pattern
Difficulty: ⭐⭐⭐⭐☆ (Intermediate)
Prerequisites: OOP Concepts, Interfaces, Composition, Dependency Injection, SOLID Principles


Table of Contents

  1. Introduction

  2. What is the Decorator Design Pattern?

  3. Why Do We Need the Decorator Pattern?

  4. The Problem with Inheritance

  5. Real-World Analogy

  6. Decorator Pattern Structure

  7. UML Class Diagram

  8. Components of the Decorator Pattern

  9. Complete C# Console Application

  10. Fluent Decorators

  11. ASP.NET Core Implementation

  12. Real-World Examples

  13. Advantages

  14. Disadvantages

  15. Best Practices

  16. Common Mistakes

  17. Decorator vs Adapter vs Proxy

  18. Middleware in ASP.NET Core (Decorator in Action)

  19. Interview Questions

  20. Summary


Introduction

One of the biggest challenges in software development is adding new functionality to existing classes without modifying their source code.

Consider an application that sends notifications.

Initially, it only supports:

  • Email

Later, business requirements change:

  • Email + Logging

  • Email + Encryption

  • Email + Validation

  • Email + Retry

  • Email + Audit

  • Email + Compression

Using inheritance, you might create classes like:

EmailNotification

LoggedEmailNotification

EncryptedEmailNotification

EncryptedLoggedEmailNotification

ValidatedEncryptedLoggedEmailNotification

RetryEncryptedLoggedEmailNotification

As features grow, the number of subclasses increases dramatically.

This is known as class explosion.

The Decorator Pattern solves this problem elegantly by allowing behaviors to be added dynamically at runtime.


What is the Decorator Design Pattern?

Definition

The Decorator Pattern is a Structural Design Pattern that allows behavior to be added to an object dynamically without modifying its existing code.

Instead of changing the original object, the object is wrapped inside one or more decorator objects, each adding additional behavior.

Think of it like wrapping a gift:

  • The gift remains the same.

  • Each wrapper adds something extra.


Why Do We Need the Decorator Pattern?

Suppose your application sends notifications.

Basic implementation:

Send Email

New requirements:

Validate

↓

Encrypt

↓

Log

↓

Retry

↓

Send Email

Without decorators, every combination would require a new subclass.

With decorators:

RetryDecorator
      ↓
LoggingDecorator
      ↓
EncryptionDecorator
      ↓
ValidationDecorator
      ↓
EmailNotification

Each decorator performs one responsibility.


The Problem with Inheritance

Imagine a coffee ordering system.

Base Coffee

Need:

  • Milk

  • Sugar

  • Chocolate

  • Cream

  • Caramel

Inheritance approach:

Coffee

MilkCoffee

SugarCoffee

MilkSugarCoffee

MilkChocolateCoffee

MilkChocolateSugarCoffee

MilkChocolateSugarCreamCoffee

The number of classes grows exponentially.

Decorators solve this by allowing ingredients to be added dynamically.


Real-World Analogy

Imagine buying a pizza.

Base Pizza

Optional Toppings

  • Cheese

  • Mushroom

  • Olive

  • Paneer

  • Corn

Each topping wraps the existing pizza.

The pizza itself never changes.

Exactly how decorators work.


Decorator Pattern Structure

The Decorator Pattern consists of:

  • Component

  • Concrete Component

  • Base Decorator

  • Concrete Decorators

  • Client

Every decorator implements the same interface as the object it decorates.


UML Class Diagram

                 +----------------------+
                 |     INotifier        |
                 +----------------------+
                 | + Send()             |
                 +----------^-----------+
                            |
          +-----------------+-----------------+
          |                                   |
+----------------------+        +---------------------------+
| EmailNotifier        |        | NotificationDecorator     |
+----------------------+        +---------------------------+
| Send()               |        | - notifier                |
+----------------------+        | + Send()                  |
                                +------------^--------------+
                                             |
              +------------------------------+-----------------------------+
              |                              |                             |
+--------------------------+    +------------------------+    +-----------------------+
| LoggingDecorator         |    | EncryptionDecorator    |    | RetryDecorator        |
+--------------------------+    +------------------------+    +-----------------------+
| Send()                   |    | Send()                 |    | Send()                |
+--------------------------+    +------------------------+    +-----------------------+

Components of the Decorator Pattern

Component

public interface INotifier
{
    void Send(string message);
}

Concrete Component

public class EmailNotifier : INotifier
{
    public void Send(string message)
    {
        Console.WriteLine($"Sending Email: {message}");
    }
}

Base Decorator

public abstract class NotificationDecorator : INotifier
{
    protected readonly INotifier notifier;

    protected NotificationDecorator(INotifier notifier)
    {
        this.notifier = notifier;
    }

    public virtual void Send(string message)
    {
        notifier.Send(message);
    }
}

Logging Decorator

public class LoggingDecorator : NotificationDecorator
{
    public LoggingDecorator(INotifier notifier)
        : base(notifier)
    {
    }

    public override void Send(string message)
    {
        Console.WriteLine("Logging Notification");

        base.Send(message);
    }
}

Encryption Decorator

public class EncryptionDecorator : NotificationDecorator
{
    public EncryptionDecorator(INotifier notifier)
        : base(notifier)
    {
    }

    public override void Send(string message)
    {
        Console.WriteLine("Encrypting Message");

        base.Send(message);
    }
}

Complete C# Console Application

class Program
{
    static void Main()
    {
        INotifier notifier =
            new LoggingDecorator(
                new EncryptionDecorator(
                    new EmailNotifier()));

        notifier.Send("Welcome");
    }
}

Output

Logging Notification

Encrypting Message

Sending Email: Welcome

Each decorator adds functionality before passing control to the next object.


Fluent Decorators

Decorators can be chained dynamically.

INotifier notifier =
    new RetryDecorator(
        new LoggingDecorator(
            new EncryptionDecorator(
                new EmailNotifier())));

You can easily add or remove decorators without changing the base class.


ASP.NET Core Implementation

One of the best examples of the Decorator Pattern is ASP.NET Core Middleware.

Each middleware:

  • Receives the request

  • Performs additional work

  • Calls the next middleware

Pipeline:

Authentication

↓

Authorization

↓

Logging

↓

Exception Handling

↓

Controller

Each middleware decorates the next one.


Example Service

public interface IReportService
{
    void Generate();
}

Base Service

public class ReportService : IReportService
{
    public void Generate()
    {
        Console.WriteLine("Generating Report");
    }
}

Logging Decorator

public class LoggingReportService : IReportService
{
    private readonly IReportService report;

    public LoggingReportService(IReportService report)
    {
        this.report = report;
    }

    public void Generate()
    {
        Console.WriteLine("Log Started");

        report.Generate();

        Console.WriteLine("Log Finished");
    }
}

Dependency Injection

builder.Services.AddScoped<ReportService>();

builder.Services.AddScoped<IReportService>(provider =>
{
    return new LoggingReportService(
        provider.GetRequiredService<ReportService>());
});

Real-World Examples

The Decorator Pattern is commonly used when behavior needs to be added dynamically.

Coffee Shop

Coffee

Milk

Sugar

Chocolate


ASP.NET Core Middleware

Request

↓

Authentication

↓

Authorization

↓

Logging

↓

Controller

Stream Classes in .NET

Examples:

  • FileStream

  • BufferedStream

  • CryptoStream

  • GZipStream

Each stream decorates another stream.


Logging

Business Service

Logging Decorator

Audit Decorator

Caching Decorator


Payment Processing

Payment

Validation

Fraud Detection

Logging

Execution


Advantages

  • Follows the Open/Closed Principle.

  • Adds functionality dynamically.

  • Eliminates subclass explosion.

  • Promotes composition over inheritance.

  • Behaviors can be combined in different ways.

  • Each decorator has a single responsibility.

  • Highly reusable.


Disadvantages

  • Many small classes may be created.

  • Debugging long decorator chains can be difficult.

  • Order of decorators can affect behavior.

  • Configuration may become complex in very large systems.


Best Practices

  • Keep decorators focused on a single responsibility.

  • Use Dependency Injection for decorator registration.

  • Avoid adding unrelated business logic.

  • Document the order of decorators when it matters.

  • Prefer composition over inheritance.


Common Mistakes

Confusing Decorator with Inheritance

Decorators wrap objects at runtime.

Inheritance extends classes at compile time.


Large Decorators

Each decorator should perform one task only.

Examples:

  • Logging

  • Validation

  • Encryption

  • Retry


Ignoring Order

The following are not equivalent:

Logging

↓

Encryption

↓

Email

vs.

Encryption

↓

Logging

↓

Email

Execution order changes the result.


Using Decorators for Object Creation

Decorators enhance behavior.

Factories create objects.

Builders construct complex objects.


Decorator vs Adapter vs Proxy

FeatureDecoratorAdapterProxy
PurposeAdd behaviorConvert interfacesControl access
Changes Interface❌ No✅ Yes❌ No
Adds Features✅ Yes❌ NoSometimes
Wraps Object✅ Yes✅ Yes✅ Yes
Primary GoalExtend functionalityCompatibilityAccess control

Middleware in ASP.NET Core (Decorator in Action)

The ASP.NET Core request pipeline is a classic implementation of the Decorator Pattern.

HTTP Request

↓

Exception Middleware

↓

Authentication Middleware

↓

Authorization Middleware

↓

Logging Middleware

↓

MVC Controller

↓

HTTP Response

Each middleware:

  • Receives the request

  • Adds behavior

  • Calls the next middleware

  • Receives the response

  • Performs additional work

This layered architecture is one of the reasons ASP.NET Core is highly modular and extensible.


Interview Questions

1. What is the Decorator Design Pattern?

A structural design pattern that dynamically adds responsibilities to an object by wrapping it inside decorator objects.


2. What problem does the Decorator Pattern solve?

It avoids class explosion caused by inheritance when adding multiple optional behaviors.


3. What is the difference between Decorator and Inheritance?

  • Inheritance extends behavior at compile time.

  • Decorator extends behavior dynamically at runtime through composition.


4. Which SOLID principles does the Decorator Pattern support?

  • Open/Closed Principle (OCP) – New behaviors can be added without modifying existing classes.

  • Single Responsibility Principle (SRP) – Each decorator has one specific responsibility.


5. Where is the Decorator Pattern used in .NET?

Common examples include:

  • ASP.NET Core Middleware

  • Stream classes (BufferedStream, CryptoStream, GZipStream)

  • Logging pipelines

  • Caching layers

  • Validation and auditing decorators


6. What is the difference between Decorator and Proxy?

  • Decorator enhances an object's behavior.

  • Proxy controls access to an object, such as lazy loading, security, or remote communication.


Summary

The Decorator Design Pattern is one of the most flexible and widely used structural patterns in modern software development. By wrapping objects with additional functionality at runtime, it allows applications to evolve without modifying existing classes or creating an excessive number of subclasses.

In C# and ASP.NET Core, the Decorator Pattern is used extensively in middleware pipelines, stream classes, logging frameworks, validation layers, caching mechanisms, and auditing solutions. Mastering this pattern helps you build modular, maintainable, and extensible applications while adhering to key SOLID principles and the philosophy of composition over inheritance.


Coming Up Next: Part 3.5 – Facade Design Pattern

In the next article, we'll explore the Facade Design Pattern, including:

  • What is the Facade Pattern?

  • Why complex subsystems need simplification

  • Facade Pattern Structure

  • UML Class Diagram

  • Complete C# Console Application

  • ASP.NET Core implementation

  • Real-world examples (Banking System, Home Theater, E-commerce Checkout)

  • Advantages and disadvantages

  • Best practices

  • Common mistakes

  • Facade vs Adapter vs Mediator

  • Interview questions

You'll learn how the Facade Pattern provides a simple, unified interface to complex subsystems, making enterprise applications easier to use, maintain, and extend.

Composite Design Pattern

 Mastering Design Patterns in C# and ASP.NET Core

Part 3.3 – Composite Design Pattern

Series: Design Patterns in C# and ASP.NET Core
Pattern Category: Structural Design Pattern
Difficulty: ⭐⭐⭐⭐☆ (Intermediate)
Prerequisites: OOP Concepts, Interfaces, Recursion, Collections, Dependency Injection


Table of Contents

  1. Introduction

  2. What is the Composite Design Pattern?

  3. Why Do We Need the Composite Pattern?

  4. The Problem with Hierarchical Structures

  5. Real-World Analogy

  6. Composite Pattern Structure

  7. UML Class Diagram

  8. Components of the Composite Pattern

  9. Complete C# Console Application

  10. ASP.NET Core Implementation

  11. Real-World Examples

  12. Advantages

  13. Disadvantages

  14. Best Practices

  15. Common Mistakes

  16. Composite vs Decorator vs Flyweight

  17. Interview Questions

  18. Summary


Introduction

Many real-world applications deal with hierarchical structures, where individual objects and groups of objects need to be treated in the same way.

Examples include:

  • File systems (Folders and Files)

  • Organization charts (Managers and Employees)

  • Menu systems (Menus and Menu Items)

  • Product categories

  • HTML/XML DOM trees

  • UI controls (Panels containing Buttons, TextBoxes, etc.)

Without a proper design pattern, developers often write separate logic for individual objects and collections of objects, resulting in duplicated code and increased complexity.

The Composite Design Pattern provides a clean solution by allowing clients to treat individual objects (Leaf nodes) and groups of objects (Composite nodes) uniformly.


What is the Composite Design Pattern?

Definition

The Composite Pattern is a Structural Design Pattern that composes objects into tree structures to represent part-whole hierarchies.

It allows clients to treat individual objects and compositions of objects uniformly.

Simply put:

A single object and a group of objects share the same interface.


Why Do We Need the Composite Pattern?

Imagine a file explorer.

Documents
│
├── Resume.docx
├── Project
│   ├── Design.pdf
│   ├── Code.zip
│   └── Notes.txt
└── Photos
    ├── Image1.jpg
    └── Image2.jpg

Without the Composite Pattern:

  • Files require one set of operations.

  • Folders require another.

  • Recursive traversal becomes complicated.

With the Composite Pattern:

  • Both File and Folder implement the same interface.

  • The client simply calls Display() without worrying about whether it's a file or a folder.


The Problem with Hierarchical Structures

Consider an organization hierarchy.

CEO
 ├── Manager A
 │      ├── Developer 1
 │      └── Developer 2
 │
 └── Manager B
        ├── Tester
        └── Designer

Without Composite:

if(employee is Manager)
{
    // Traverse subordinates
}
else
{
    // Display employee
}

Every new hierarchy requires additional conditional logic.

Composite eliminates these checks by treating every node the same way.


Real-World Analogy

Think of a company organization chart.

  • An Employee may work individually.

  • A Manager is also an employee but manages a team.

Both are employees.

Similarly:

  • A File is an item.

  • A Folder is also an item that contains other items.

The client interacts with both using the same interface.


Composite Pattern Structure

The Composite Pattern consists of:

  • Component – Common interface

  • Leaf – Individual object

  • Composite – Collection of components

  • Client – Uses the component interface


UML Class Diagram

                    +----------------------+
                    |     Component        |
                    +----------------------+
                    | + Display()          |
                    +----------^-----------+
                               |
          +--------------------+--------------------+
          |                                         |
+----------------------+              +----------------------+
|       File           |              |      Folder          |
+----------------------+              +----------------------+
| Display()            |              | Add()               |
|                      |              | Remove()            |
+----------------------+              | Display()           |
                                      +---------+-----------+
                                                |
                                     Contains many Components

Components of the Composite Pattern

Component

Defines the common interface.

public interface IFileSystem
{
    void Display();
}

Leaf

Represents an individual object.

public class File : IFileSystem
{
    public string Name { get; }

    public File(string name)
    {
        Name = name;
    }

    public void Display()
    {
        Console.WriteLine($"File : {Name}");
    }
}

Composite

Represents a collection of components.

public class Folder : IFileSystem
{
    public string Name { get; }

    private readonly List<IFileSystem> items = new();

    public Folder(string name)
    {
        Name = name;
    }

    public void Add(IFileSystem item)
    {
        items.Add(item);
    }

    public void Remove(IFileSystem item)
    {
        items.Remove(item);
    }

    public void Display()
    {
        Console.WriteLine($"Folder : {Name}");

        foreach (var item in items)
        {
            item.Display();
        }
    }
}

Complete C# Console Application

class Program
{
    static void Main()
    {
        Folder root = new Folder("Documents");

        root.Add(new File("Resume.docx"));
        root.Add(new File("Profile.pdf"));

        Folder project = new Folder("Project");

        project.Add(new File("Design.pdf"));
        project.Add(new File("Code.zip"));

        root.Add(project);

        root.Display();
    }
}

Output

Folder : Documents
File : Resume.docx
File : Profile.pdf
Folder : Project
File : Design.pdf
File : Code.zip

Notice that both File and Folder are treated exactly the same through the IFileSystem interface.


ASP.NET Core Implementation

Suppose you're building a dynamic navigation menu.

Component

public interface IMenuComponent
{
    string Render();
}

Leaf

public class MenuItem : IMenuComponent
{
    public string Name { get; set; }

    public string Render()
    {
        return $"Menu : {Name}";
    }
}

Composite

public class MenuGroup : IMenuComponent
{
    public string Name { get; set; }

    private readonly List<IMenuComponent> menus = new();

    public void Add(IMenuComponent menu)
    {
        menus.Add(menu);
    }

    public string Render()
    {
        StringBuilder builder = new();

        builder.AppendLine(Name);

        foreach (var menu in menus)
        {
            builder.AppendLine(menu.Render());
        }

        return builder.ToString();
    }
}

The Composite Pattern makes it easy to render nested menus regardless of depth.


Real-World Examples

The Composite Pattern is ideal for representing tree-like structures.

File Explorer

Folder
    File
    File
    Folder
        File

Organization Hierarchy

CEO
    Manager
        Developer
        Tester

HTML DOM

html
 ├── head
 ├── body
 │      ├── div
 │      ├── button
 │      └── table

Product Categories

Electronics
    Laptop
    Mobile
    TV

Menu Systems

Admin
    Users
    Roles
    Permissions

UI Controls

Panel
    Button
    Label
    TextBox

Advantages

  • Treats individual objects and collections uniformly.

  • Simplifies client code.

  • Supports recursive tree structures naturally.

  • Makes adding new component types easier.

  • Promotes the Open/Closed Principle.

  • Reduces conditional statements.

  • Improves maintainability.


Disadvantages

  • Can make the design overly generic.

  • Difficult to restrict which components can contain children.

  • Deep hierarchies may affect performance.

  • Debugging recursive structures can be more challenging.


Best Practices

  • Use Composite when representing part-whole hierarchies.

  • Keep the Component interface simple and focused.

  • Use recursion carefully to avoid stack overflows with extremely deep trees.

  • Hide collection management from the client when possible.

  • Apply Dependency Injection for composite services in ASP.NET Core.


Common Mistakes

Adding Child Methods to Leaf Objects

Leaf objects should not expose Add() or Remove() methods if they cannot contain children.


Overusing Composite

Not every parent-child relationship requires the Composite Pattern. Use it only when clients need to treat single objects and groups uniformly.


Mixing Business Logic

Composite classes should focus on managing child components, not unrelated business operations.


Ignoring Performance

Very deep recursive hierarchies may require optimization or iterative traversal.


Composite vs Decorator vs Flyweight

FeatureCompositeDecoratorFlyweight
PurposeRepresent tree structuresAdd behavior dynamicallyShare objects to reduce memory
Uses Recursion✅ Yes❌ No❌ No
Parent–Child Relationship✅ Yes❌ No❌ No
Object Sharing❌ No❌ No✅ Yes
Runtime Behavior Extension❌ No✅ Yes❌ No

Interview Questions

1. What is the Composite Design Pattern?

A structural design pattern that composes objects into tree structures, allowing clients to treat individual objects and groups uniformly.


2. When should you use the Composite Pattern?

When working with hierarchical structures such as folders, menus, organization charts, or UI controls.


3. What is the difference between a Leaf and a Composite?

  • Leaf represents an individual object and cannot contain children.

  • Composite represents a group of objects and can contain both Leaf and Composite objects.


4. Which SOLID principle does the Composite Pattern support?

The Open/Closed Principle (OCP) because new component types can be introduced without modifying existing client code.


5. Is recursion commonly used in the Composite Pattern?

Yes. Recursive traversal is one of the key characteristics of the Composite Pattern.


6. What are common real-world examples?

  • File systems

  • HTML DOM

  • Organization charts

  • Product categories

  • Navigation menus

  • UI component hierarchies


Summary

The Composite Design Pattern is one of the most effective ways to model hierarchical data structures. By allowing individual objects and groups of objects to share a common interface, it simplifies client code, reduces conditional logic, and makes recursive operations straightforward.

In enterprise C# and ASP.NET Core applications, the Composite Pattern is widely used for file systems, menu structures, organization hierarchies, UI components, and document object models. Mastering this pattern will help you design scalable, maintainable applications that work naturally with tree-like data.


Coming Up Next: Part 3.4 – Decorator Design Pattern

In the next article, we'll explore the Decorator Design Pattern, including:

  • What is the Decorator Pattern?

  • Why inheritance is not always the best choice

  • Dynamically adding behavior to objects

  • Decorator Pattern Structure

  • UML Class Diagram

  • Complete C# Console Application

  • ASP.NET Core implementation

  • Real-world examples (Coffee Shop, Notification System, ASP.NET Core Middleware)

  • Advantages and disadvantages

  • Best practices

  • Common mistakes

  • Interview questions

You'll learn how the Decorator Pattern enables you to extend object functionality dynamically without modifying existing classes, making it one of the most widely used structural patterns in modern C# and ASP.NET Core applications.

Bridge Design Pattern

 Mastering Design Patterns in C# and ASP.NET Core

Part 3.2 – Bridge Design Pattern

Series: Design Patterns in C# and ASP.NET Core
Pattern Category: Structural Design Pattern
Difficulty: ⭐⭐⭐⭐☆ (Intermediate)
Prerequisites: OOP Concepts, Interfaces, Abstract Classes, Composition, Dependency Injection


Table of Contents

  1. Introduction

  2. What is the Bridge Design Pattern?

  3. Why Do We Need the Bridge Pattern?

  4. The Problem with Inheritance

  5. Real-World Analogy

  6. Bridge Pattern Structure

  7. UML Class Diagram

  8. Components of the Bridge Pattern

  9. Complete C# Console Application

  10. ASP.NET Core Implementation

  11. Real-World Examples

  12. Advantages

  13. Disadvantages

  14. Best Practices

  15. Common Mistakes

  16. Bridge vs Adapter vs Strategy

  17. Interview Questions

  18. Summary


Introduction

Inheritance is one of the most fundamental concepts in Object-Oriented Programming (OOP). It allows us to reuse code and create specialized classes from existing ones.

However, as applications grow, relying solely on inheritance can lead to an explosion of classes.

Imagine you're building a notification system that supports:

Notification Types

  • Email

  • SMS

  • Push Notification

Providers

  • Azure Communication Services

  • Twilio

  • SendGrid

If you use inheritance for every combination, you might end up with classes like:

AzureEmailNotification
AzureSmsNotification
AzurePushNotification

TwilioEmailNotification
TwilioSmsNotification
TwilioPushNotification

SendGridEmailNotification
SendGridSmsNotification
SendGridPushNotification

With just 3 notification types and 3 providers, you've already created 9 classes.

If tomorrow another provider is introduced, the number of classes increases significantly.

The Bridge Pattern solves this problem by separating the abstraction from its implementation.


What is the Bridge Design Pattern?

Definition

The Bridge Pattern is a Structural Design Pattern that separates an abstraction from its implementation so that both can evolve independently.

Instead of combining every abstraction with every implementation through inheritance, the Bridge Pattern connects them using composition.

This significantly reduces the number of classes and improves flexibility.


Why Do We Need the Bridge Pattern?

Suppose your application supports multiple report types:

  • Sales Report

  • Employee Report

  • Inventory Report

Each report can be exported as:

  • PDF

  • Excel

  • Word

Without the Bridge Pattern, you might create:

SalesPdfReport
SalesExcelReport
SalesWordReport

EmployeePdfReport
EmployeeExcelReport
EmployeeWordReport

InventoryPdfReport
InventoryExcelReport
InventoryWordReport

As report types or export formats grow, the number of classes increases rapidly.

Instead, we can separate:

  • Report (Abstraction)

  • Exporter (Implementation)

Any report can now work with any exporter at runtime.


The Problem with Inheritance

Consider two independent dimensions:

  • Notification Type

  • Provider

If each notification type inherits from each provider, the design becomes rigid.

Problems include:

  • Too many classes

  • Difficult maintenance

  • Code duplication

  • Limited flexibility

  • Violates the principle of Composition over Inheritance

The Bridge Pattern addresses this by connecting abstractions and implementations through interfaces.


Real-World Analogy

Think of a television and its remote control.

Different TV brands:

  • Samsung

  • Sony

  • LG

Different remote types:

  • Basic Remote

  • Smart Remote

  • Voice Remote

You don't create separate remotes for every TV model.

Instead:

  • The remote (abstraction) communicates with the TV (implementation).

  • Either the remote or the TV can change independently.

This is the essence of the Bridge Pattern.


Bridge Pattern Structure

The Bridge Pattern consists of:

  • Abstraction

  • Refined Abstraction

  • Implementor

  • Concrete Implementor

The abstraction holds a reference to the implementor instead of inheriting from it.


UML Class Diagram

                +----------------------+
                |     IExporter        |
                +----------------------+
                | + Export(string)     |
                +----------^-----------+
                           |
          +----------------+----------------+
          |                                 |
+----------------------+         +----------------------+
| PdfExporter          |         | ExcelExporter        |
+----------------------+         +----------------------+
| Export()             |         | Export()             |
+----------------------+         +----------------------+

                 Composition
                      ▲
                      |
            +----------------------+
            |      Report          |
            +----------------------+
            | - exporter           |
            | + Generate()         |
            +----------^-----------+
                       |
          +------------+-------------+
          |                          |
+----------------------+   +----------------------+
| SalesReport          |   | EmployeeReport      |
+----------------------+   +----------------------+
| Generate()           |   | Generate()          |
+----------------------+   +----------------------+

Components of the Bridge Pattern

Implementor

Defines the implementation interface.

public interface IExporter
{
    void Export(string data);
}

Concrete Implementors

public class PdfExporter : IExporter
{
    public void Export(string data)
    {
        Console.WriteLine($"Exporting PDF: {data}");
    }
}

public class ExcelExporter : IExporter
{
    public void Export(string data)
    {
        Console.WriteLine($"Exporting Excel: {data}");
    }
}

Abstraction

public abstract class Report
{
    protected readonly IExporter exporter;

    protected Report(IExporter exporter)
    {
        this.exporter = exporter;
    }

    public abstract void Generate();
}

Refined Abstraction

public class SalesReport : Report
{
    public SalesReport(IExporter exporter)
        : base(exporter)
    {
    }

    public override void Generate()
    {
        exporter.Export("Sales Report");
    }
}

Complete C# Console Application

class Program
{
    static void Main()
    {
        Report report = new SalesReport(new PdfExporter());

        report.Generate();

        report = new SalesReport(new ExcelExporter());

        report.Generate();
    }
}

Output

Exporting PDF: Sales Report

Exporting Excel: Sales Report

Notice that the SalesReport class never changes.

Only the exporter changes.


ASP.NET Core Implementation

Suppose an ASP.NET Core application generates invoices.

Invoices can be exported to:

  • PDF

  • Excel

Export Interface

public interface IInvoiceExporter
{
    void Export(string invoice);
}

Exporters

public class PdfInvoiceExporter : IInvoiceExporter
{
    public void Export(string invoice)
    {
        Console.WriteLine($"PDF Invoice: {invoice}");
    }
}

public class ExcelInvoiceExporter : IInvoiceExporter
{
    public void Export(string invoice)
    {
        Console.WriteLine($"Excel Invoice: {invoice}");
    }
}

Invoice Service

public class InvoiceService
{
    private readonly IInvoiceExporter _exporter;

    public InvoiceService(IInvoiceExporter exporter)
    {
        _exporter = exporter;
    }

    public void GenerateInvoice()
    {
        _exporter.Export("Invoice #1001");
    }
}

Dependency Injection

builder.Services.AddScoped<IInvoiceExporter, PdfInvoiceExporter>();

builder.Services.AddScoped<InvoiceService>();

Changing the registration to:

builder.Services.AddScoped<IInvoiceExporter, ExcelInvoiceExporter>();

changes the implementation without modifying the business logic.


Real-World Examples

The Bridge Pattern is commonly used when two dimensions of variation should evolve independently.

Document Generation

Reports:

  • Sales

  • Employee

  • Inventory

Formats:

  • PDF

  • Excel

  • Word


Notification Systems

Notifications:

  • Email

  • SMS

  • Push

Providers:

  • Azure Communication Services

  • Twilio

  • SendGrid


Payment Processing

Payment Types:

  • Card

  • UPI

  • Wallet

Providers:

  • Stripe

  • Razorpay

  • PayPal


Logging Systems

Log Types:

  • File

  • Database

  • Cloud

Frameworks:

  • Serilog

  • NLog

  • Microsoft Logging


Cloud Storage

Storage Operations:

  • Upload

  • Download

  • Delete

Providers:

  • Azure Blob Storage

  • Amazon S3

  • Google Cloud Storage


Advantages

  • Reduces class explosion.

  • Separates abstraction from implementation.

  • Promotes composition over inheritance.

  • Supports the Open/Closed Principle.

  • Improves maintainability.

  • Allows runtime switching of implementations.

  • Encourages loose coupling.


Disadvantages

  • Introduces additional abstractions and interfaces.

  • Can increase the number of classes for simple applications.

  • Requires careful design to identify independent dimensions.


Best Practices

  • Use the Bridge Pattern when there are two or more independent dimensions of variation.

  • Prefer composition instead of inheritance.

  • Inject implementors using Dependency Injection.

  • Keep abstractions focused on business behavior and implementors focused on technical details.

  • Avoid adding business logic to implementor classes.


Common Mistakes

Confusing Bridge with Adapter

The Adapter Pattern makes incompatible interfaces work together.

The Bridge Pattern separates abstraction from implementation.


Overusing Inheritance

If you're creating many subclasses just to support combinations of features, consider using the Bridge Pattern instead.


Mixing Responsibilities

Keep the abstraction responsible for business operations and the implementor responsible for execution details.


Using Bridge for Simple Designs

If there is only one implementation and no expectation of future variation, the Bridge Pattern may add unnecessary complexity.


Bridge vs Adapter vs Strategy

FeatureBridgeAdapterStrategy
PurposeSeparate abstraction and implementationConvert incompatible interfacesEncapsulate interchangeable algorithms
Uses Composition✅ Yes✅ Yes✅ Yes
Changes Interface❌ No✅ Yes❌ No
Runtime FlexibilityHighModerateHigh
Primary GoalAvoid class explosionIntegrationAlgorithm selection

Interview Questions

1. What is the Bridge Design Pattern?

A structural design pattern that separates an abstraction from its implementation so that both can evolve independently.


2. What problem does the Bridge Pattern solve?

It prevents a combinatorial explosion of subclasses when there are multiple independent dimensions of variation.


3. What is the difference between Bridge and Adapter?

  • Bridge is designed during system architecture to separate abstractions from implementations.

  • Adapter is typically introduced later to make incompatible interfaces work together.


4. Which SOLID principles does the Bridge Pattern support?

  • Open/Closed Principle (OCP): New abstractions and implementations can be added without modifying existing code.

  • Dependency Inversion Principle (DIP): Abstractions depend on interfaces rather than concrete implementations.


5. Where is the Bridge Pattern commonly used?

  • Report generation

  • Notification systems

  • Payment providers

  • Logging frameworks

  • Cloud storage abstractions

  • Cross-platform applications


6. Why is composition preferred over inheritance in the Bridge Pattern?

Composition provides greater flexibility, reduces coupling, and allows implementations to be changed at runtime without modifying the abstraction.


Summary

The Bridge Design Pattern is a powerful structural pattern that separates what an object does (abstraction) from how it does it (implementation). By connecting the two through composition instead of inheritance, it prevents class explosion, improves flexibility, and supports scalable application design.

In modern C# and ASP.NET Core applications, the Bridge Pattern is particularly valuable when integrating multiple providers, supporting different output formats, or designing systems with independently evolving dimensions.

Mastering the Bridge Pattern will help you create cleaner architectures that are easier to extend, maintain, and test as business requirements grow.


Coming Up Next: Part 3.3 – Composite Design Pattern

In the next article, we'll explore the Composite Design Pattern, including:

  • What is the Composite Pattern?

  • Tree Structures and Hierarchical Objects

  • Parent–Child Relationships

  • Leaf vs. Composite Objects

  • UML Class Diagram

  • Complete C# Console Application

  • ASP.NET Core implementation

  • Real-world examples (File System, Organization Hierarchy, Menu Systems)

  • Advantages and disadvantages

  • Best practices

  • Common mistakes

  • Interview questions

You'll learn how the Composite Pattern enables you to treat individual objects and groups of objects uniformly, making it ideal for representing hierarchical structures such as folders, menus, organizational charts, and UI components.

Adapter Design Pattern

 Mastering Design Patterns in C# and ASP.NET Core

Part 3.1 – Adapter Design Pattern

Series: Design Patterns in C# and ASP.NET Core
Pattern Category: Structural Design Pattern
Difficulty: ⭐⭐⭐☆☆ (Intermediate)
Prerequisites: OOP Concepts, Interfaces, Inheritance, Composition, Dependency Injection


Table of Contents

  1. Introduction

  2. What is the Adapter Design Pattern?

  3. Why Do We Need the Adapter Pattern?

  4. The Problem with Incompatible Interfaces

  5. Real-World Analogy

  6. Types of Adapter Pattern

  7. Object Adapter vs. Class Adapter

  8. UML Class Diagram

  9. Components of the Adapter Pattern

  10. Complete C# Console Application

  11. ASP.NET Core Implementation

  12. Real-World Examples

  13. Advantages

  14. Disadvantages

  15. Best Practices

  16. Common Mistakes

  17. Adapter vs Decorator vs Facade

  18. Interview Questions

  19. Summary


Introduction

Modern software rarely exists in isolation. Most enterprise applications communicate with:

  • Third-party payment gateways

  • External REST APIs

  • Legacy applications

  • Cloud services

  • Different databases

  • Vendor SDKs

Unfortunately, these systems are often built using different technologies and expose different interfaces.

For example:

  • Your application expects a method named ProcessPayment()

  • A third-party SDK provides a method named MakeTransaction()

Although both methods perform the same operation, their interfaces are incompatible.

Rewriting the third-party library is usually impossible.

Changing your application's architecture may not be practical.

So how do you make two incompatible systems work together?

The answer is the Adapter Design Pattern.


What is the Adapter Design Pattern?

Definition

The Adapter Pattern is a Structural Design Pattern that allows two incompatible interfaces to work together by introducing an intermediate object called an Adapter.

The Adapter converts one interface into another that the client expects, without modifying the existing code.

Think of it as a translator between two systems.


Why Do We Need the Adapter Pattern?

Imagine developing an online shopping application.

Your application uses:

IPaymentGateway
void Pay(decimal amount);

Later, the company decides to integrate a third-party payment provider.

The SDK exposes:

void MakePayment(decimal amount);

The method names differ, but both perform the same task.

Without an Adapter:

❌ Your application cannot directly use the SDK.

You have two options:

  • Modify your application (expensive)

  • Modify the SDK (impossible)

The Adapter Pattern introduces a bridge between them.


The Problem with Incompatible Interfaces

Suppose your application expects:

public interface IMessageService
{
    void Send(string message);
}

But the vendor library provides:

public class SmsProvider
{
    public void SendSMS(string text)
    {
        Console.WriteLine(text);
    }
}

Your application cannot call:

Send()

because only:

SendSMS()

exists.

Instead of changing either class, we create an Adapter.


Real-World Analogy

Imagine traveling from India to the United States.

Your laptop charger has a two-pin Indian plug.

The U.S. power outlet uses a different socket.

You don't replace:

  • Your laptop

  • The wall socket

Instead, you use a power adapter.

The adapter converts one interface into another.

Similarly:

Application → Adapter → Third-party System


Types of Adapter Pattern

There are two common implementations.

1. Object Adapter (Recommended)

Uses Composition.

Client

↓

Adapter

↓

Existing Class

This is the most common approach in C#.


2. Class Adapter

Uses Inheritance.

Adapter

↓

Existing Class

Since C# does not support multiple class inheritance, this approach is less common and is often simulated with interfaces.


Object Adapter vs Class Adapter

FeatureObject AdapterClass Adapter
UsesCompositionInheritance
FlexibilityHighModerate
Supports Existing ObjectsYesLimited
Preferred in C#✅ Yes⚠ Rare
Follows Composition over Inheritance✅ Yes❌ No

Recommendation: In C#, prefer the Object Adapter because it aligns with the principle of Composition over Inheritance.


UML Class Diagram

                   +-------------------+
                   |      Client       |
                   +-------------------+
                             |
                             |
                             V
                   +-------------------+
                   |      ITarget      |
                   +-------------------+
                   | + Request()       |
                   +---------^---------+
                             |
                    Implements
                             |
                   +-------------------+
                   |      Adapter      |
                   +-------------------+
                   | - adaptee         |
                   | + Request()       |
                   +---------+---------+
                             |
                             |
                             V
                   +-------------------+
                   |     Adaptee       |
                   +-------------------+
                   | + SpecificRequest()|
                   +-------------------+

Components of the Adapter Pattern

Client

Uses the target interface.


Target Interface

The interface expected by the client.

public interface ITarget
{
    void Request();
}

Adaptee

Existing class with an incompatible interface.

public class Adaptee
{
    public void SpecificRequest()
    {
        Console.WriteLine("Specific Request Executed");
    }
}

Adapter

Converts one interface into another.

public class Adapter : ITarget
{
    private readonly Adaptee _adaptee;

    public Adapter(Adaptee adaptee)
    {
        _adaptee = adaptee;
    }

    public void Request()
    {
        _adaptee.SpecificRequest();
    }
}

Complete C# Console Application

Target Interface

public interface INotification
{
    void Send(string message);
}

Existing Third-Party Library

public class EmailVendor
{
    public void SendEmail(string message)
    {
        Console.WriteLine($"Vendor Email: {message}");
    }
}

Adapter

public class EmailAdapter : INotification
{
    private readonly EmailVendor _vendor;

    public EmailAdapter(EmailVendor vendor)
    {
        _vendor = vendor;
    }

    public void Send(string message)
    {
        _vendor.SendEmail(message);
    }
}

Client

class Program
{
    static void Main()
    {
        INotification notification =
            new EmailAdapter(new EmailVendor());

        notification.Send("Welcome User");
    }
}

Output

Vendor Email: Welcome User

ASP.NET Core Implementation

Suppose your application expects:

public interface INotificationService
{
    void Send(string message);
}

Third-party SDK

public class TwilioProvider
{
    public void SendSms(string text)
    {
        Console.WriteLine($"SMS : {text}");
    }
}

Adapter

public class TwilioAdapter : INotificationService
{
    private readonly TwilioProvider _provider;

    public TwilioAdapter(TwilioProvider provider)
    {
        _provider = provider;
    }

    public void Send(string message)
    {
        _provider.SendSms(message);
    }
}

Dependency Injection

builder.Services.AddSingleton<TwilioProvider>();

builder.Services.AddScoped<INotificationService, TwilioAdapter>();

Controller

[ApiController]
[Route("api/[controller]")]
public class NotificationController : ControllerBase
{
    private readonly INotificationService _notification;

    public NotificationController(
        INotificationService notification)
    {
        _notification = notification;
    }

    [HttpPost]
    public IActionResult Send()
    {
        _notification.Send("Order Confirmed");

        return Ok();
    }
}

The controller never knows it's communicating with a third-party SDK.


Real-World Examples

The Adapter Pattern is used extensively in enterprise applications.

Payment Gateway Integration

Converting different payment provider APIs into a common payment interface.

Examples:

  • Stripe

  • PayPal

  • Razorpay

  • Square


Legacy System Integration

Adapting older SOAP/XML services to modern REST-based applications.


Cloud Storage Providers

Providing a common interface for:

  • Azure Blob Storage

  • Amazon S3

  • Google Cloud Storage


Logging Frameworks

Creating a unified logging interface while using:

  • Serilog

  • NLog

  • log4net

  • Microsoft.Extensions.Logging


Database Providers

Supporting:

  • SQL Server

  • Oracle

  • PostgreSQL

  • MySQL

through a common repository abstraction.


External APIs

Adapting third-party APIs to internal domain models without exposing vendor-specific details.


Advantages

  • Enables incompatible interfaces to work together.

  • Promotes code reusability.

  • Keeps existing code unchanged.

  • Supports the Open/Closed Principle.

  • Simplifies integration with third-party libraries.

  • Reduces coupling between client code and external systems.

  • Improves maintainability.


Disadvantages

  • Introduces additional classes.

  • Adds a small layer of indirection.

  • Can become difficult to manage if too many adapters are created.

  • Poorly designed adapters may hide underlying API limitations.


Best Practices

  • Prefer Object Adapter over Class Adapter.

  • Keep adapters lightweight and focused solely on interface conversion.

  • Do not add business logic to adapters.

  • Use Dependency Injection to manage adapters in ASP.NET Core.

  • Document any behavioral differences between the target interface and the adaptee.


Common Mistakes

Using Adapter for New Features

The Adapter Pattern is intended for integrating existing incompatible interfaces, not for implementing entirely new functionality.


Embedding Business Logic

Adapters should translate interfaces, not perform business operations.


Overusing Adapters

Not every interface mismatch requires an adapter. Evaluate whether simple refactoring or standardization is sufficient.


Tight Coupling to Vendor APIs

Avoid exposing vendor-specific classes to the rest of the application. Keep them encapsulated within the adapter.


Adapter vs Decorator vs Facade

FeatureAdapterDecoratorFacade
PurposeConverts interfacesAdds behaviorSimplifies a subsystem
Changes Interface✅ Yes❌ NoUsually No
Adds Functionality❌ No✅ YesSometimes
Simplifies UsageIndirectlyNo✅ Yes
Common Use CaseThird-party integrationDynamic feature extensionComplex subsystem simplification

Interview Questions

1. What is the Adapter Design Pattern?

A structural design pattern that allows incompatible interfaces to work together by introducing an adapter.


2. When should you use the Adapter Pattern?

When integrating legacy systems, third-party libraries, external APIs, or vendor SDKs with interfaces that differ from those expected by your application.


3. What is the difference between Object Adapter and Class Adapter?

  • Object Adapter uses composition and is the preferred approach in C#.

  • Class Adapter uses inheritance and is less common due to C#'s single inheritance model.


4. Which SOLID principles does the Adapter Pattern support?

  • Open/Closed Principle (OCP): New adapters can be added without modifying existing client code.

  • Dependency Inversion Principle (DIP): Clients depend on abstractions rather than concrete implementations.


5. Can the Adapter Pattern improve testability?

Yes. Because clients depend on interfaces, adapters can be replaced with mock implementations during unit testing.


6. What are common real-world examples of the Adapter Pattern?

  • Payment gateway integrations

  • Cloud storage providers

  • Legacy system modernization

  • External REST or SOAP APIs

  • Logging frameworks

  • Database providers


Summary

The Adapter Design Pattern is one of the most practical and widely used structural patterns in software development. It enables applications to integrate with incompatible systems by converting one interface into another without modifying existing code.

In enterprise C# and ASP.NET Core applications, the Adapter Pattern is commonly used to integrate third-party libraries, vendor SDKs, cloud services, and legacy systems while maintaining clean architecture and adherence to SOLID principles.

By understanding when and how to apply the Adapter Pattern, you can build systems that are more flexible, maintainable, and easier to extend as business requirements evolve.


Coming Up Next: Part 3.2 – Bridge Design Pattern

In the next article, we'll explore the Bridge Design Pattern, where you'll learn:

  • What is the Bridge Pattern?

  • Why inheritance alone is not enough

  • Abstraction vs. Implementation

  • Composition over Inheritance

  • UML Class Diagram

  • Complete C# Console Application

  • ASP.NET Core implementation

  • Real-world examples

  • Advantages and disadvantages

  • Best practices

  • Common mistakes

  • Interview questions

You'll discover how the Bridge Pattern decouples abstractions from their implementations, allowing both to evolve independently—making it especially valuable for scalable, maintainable enterprise applications.

Monday, July 6, 2026

How to Configure DevOps Pipelines: A Real-Time CI/CD Example

In moern software teams, DevOps pipelines are the backbone that connects code to production. Instead of manually building, testing, and deploying applications, we automate these steps into a smooth flow known as a CI/CD pipeline. This article explains what a DevOps pipeline is and walks through a real-time example you can use for your own projects.


What Is a DevOps Pipeline?

A DevOps pipeline is a series of automated steps that take your application from source code to a running, monitored service. It usually includes:

  • Code: Developers push changes to a version control system like Git.

  • Build: The pipeline compiles/assembles the application.

  • Test: Automated tests run to catch bugs early.

  • Release: Successful builds are packaged as deployable artifacts.

  • Deploy: Artifacts are deployed to environments (dev, test, production).

  • Monitor: Logs and metrics are collected to ensure the system is healthy.

When all these stages run automatically on every code change, you get Continuous Integration (CI) and Continuous Delivery/Deployment (CD).


Why Pipelines Matter in Real Projects

In a real-time project, multiple developers work on the same codebase. Without a pipeline:

  • Builds may fail on one machine but not another.

  • Manual deployments are error-prone and slow.

  • Bugs reach production because tests are skipped.

With a CI/CD pipeline:

  • Every push triggers a consistent build and test process.

  • Deployments follow the same automated, repeatable steps.

  • You get faster feedback and higher confidence in releases.

For teams building web applications, APIs, or microservices, a good pipeline is not optional—it is essential.


Real-Time Scenario: Web App CI/CD Pipeline

Let’s take a practical example you can relate to.

Imagine a small team building a Node.js web application. The code is stored in a Git repository. The team wants this behavior:

  1. Developer pushes code to the main branch.

  2. Pipeline automatically runs:

    • Install dependencies

    • Run unit tests

    • Build the application

  3. If everything passes, the pipeline deploys the app to a cloud Web App (for example, Azure Web App or any similar platform).

  4. The team can see logs and monitor the application after every deployment.

This is a classic real-time CI/CD pipeline scenario.


Designing the Pipeline Stages

To implement this behavior, we can design the pipeline with two main stages:

1. Build and Test (CI)

This stage should:

  • Check out the latest code.

  • Install required tools and dependencies.

  • Run automated tests.

  • Create a build output ready for deployment.

If any step fails, the pipeline should stop and notify the team.

2. Deploy (CD)

This stage should:

  • Take the successful build artifact.

  • Deploy it to the chosen environment (dev, test, or production).

  • Optionally run post-deployment checks (smoke tests).

  • Notify the team on success or failure.

Separating Build and Deploy stages makes it easier to control which environments receive which build.


Example Pipeline Configuration (YAML Style)

Many modern tools (Azure DevOps, GitHub Actions, GitLab CI, etc.) use YAML configuration files to define pipelines. Below is a simplified pipeline configuration that you can adapt for your own blog readers.

text
trigger: branches: include: - main pool: vmImage: 'ubuntu-latest' variables: appName: 'my-demo-webapp' environment: 'production' stages: - stage: Build displayName: 'Build and Test' jobs: - job: BuildJob steps: - checkout: self - task: NodeTool@0 inputs: versionSpec: '18.x' displayName: 'Use Node.js 18' - script: | npm install npm test npm run build displayName: 'Install, test, and build' - task: ArchiveFiles@2 inputs: rootFolderOrFile: 'dist' includeRootFolder: false archiveType: 'zip' archiveFile: '$(Build.ArtifactStagingDirectory)/drop.zip' displayName: 'Archive build output' - task: PublishBuildArtifacts@1 inputs: pathtoPublish: '$(Build.ArtifactStagingDirectory)' artifactName: 'drop' displayName: 'Publish artifact' - stage: Deploy displayName: 'Deploy to Production' dependsOn: Build jobs: - job: DeployJob steps: - download: current artifact: drop - script: | echo "Deploying $(appName) to $(environment) environment" # Replace this with your actual deploy command, e.g.: # az webapp deploy --name $(appName) --src-path "$(Pipeline.Workspace)/drop/drop.zip" displayName: 'Deploy application'

You can explain this YAML in your blog as follows:

  • trigger ensures the pipeline runs on every push to main.

  • pool defines the build agent (Ubuntu in this example).

  • The Build stage:

    • Checks out code.

    • Uses Node.js 18.

    • Runs install, tests, and build.

    • Archives and publishes the build as an artifact.

  • The Deploy stage:

    • Downloads the artifact.

    • Runs a deployment command (to a cloud platform or server).


How This Works in Real Time

In day-to-day development, this pipeline behaves like an automated assistant:

  • A developer fixes a bug and pushes code.

  • Within minutes, the pipeline:

    • Builds the app.

    • Runs tests.

    • Deploys a new version if everything passes.

  • If a test fails, the pipeline stops and marks the run as failed.

  • The team checks the pipeline logs, fixes the issue, and pushes again.

This continuous loop greatly reduces the risk of broken code reaching production and makes releases faster and safer.


Best Practices for DevOps Pipelines

To make your pipelines robust for real-world use, consider these practices:

  • Keep pipelines fast: Slow pipelines reduce developer productivity.

  • Fail early: Run quick linting and unit tests before long integration tests.

  • Use separate environments: Deploy to dev, then test, then production with approvals.

  • Add quality gates: Include code quality checks, security scans, and test coverage thresholds.

  • Monitor everything: Collect logs and metrics from both pipelines and applications.

These small improvements add up to a professional, corporate-grade pipeline over time.


Don't Copy

Protected by Copyscape Online Plagiarism Checker