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
Introduction
What is the Adapter Design Pattern?
Why Do We Need the Adapter Pattern?
The Problem with Incompatible Interfaces
Real-World Analogy
Types of Adapter Pattern
Object Adapter vs. Class Adapter
UML Class Diagram
Components of the Adapter Pattern
Complete C# Console Application
ASP.NET Core Implementation
Real-World Examples
Advantages
Disadvantages
Best Practices
Common Mistakes
Adapter vs Decorator vs Facade
Interview Questions
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
| Feature | Object Adapter | Class Adapter |
|---|---|---|
| Uses | Composition | Inheritance |
| Flexibility | High | Moderate |
| Supports Existing Objects | Yes | Limited |
| 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
| Feature | Adapter | Decorator | Facade |
|---|---|---|---|
| Purpose | Converts interfaces | Adds behavior | Simplifies a subsystem |
| Changes Interface | ✅ Yes | ❌ No | Usually No |
| Adds Functionality | ❌ No | ✅ Yes | Sometimes |
| Simplifies Usage | Indirectly | No | ✅ Yes |
| Common Use Case | Third-party integration | Dynamic feature extension | Complex 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.
No comments:
Post a Comment