Monday, July 13, 2026

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.

Don't Copy

Protected by Copyscape Online Plagiarism Checker