Learn about Adapter Pattern - a design pattern that helps incompatible classes work together seamlessly. Detailed guide with practical C# examples and comparison between Object Adapter vs Class Adapter.

No table of contents available for this article
Adapter Pattern (also known as Wrapper Pattern) is a design pattern belonging to the Structural Pattern family - patterns that focus on organizing code structure.
Think of it simply: you have an iPhone charger (Lightning port) but want to charge an Android phone (USB-C port). These two ports are incompatible. The solution? You need a converter (adapter). That's the core idea of Adapter Pattern in programming!
Formal Definition: Adapter Pattern converts the interface of a class into another interface that clients expect. It allows classes with incompatible interfaces to work together without modifying the original code.
Role of Adapter:
Acts as a mediator between two incompatible classes
Converts the interface of an existing class into the interface clients need
Enables communication between classes without modifying original code
Real-world Problem: Imagine you're building a stock market tracking application. Your current app:
Downloads data from multiple sources in XML format
Processes and displays beautiful charts and diagrams for users
One fine day, you discover a smart analytics library from a third party that can predict stock trends with incredible accuracy. But there's a big problem: this library only works with JSON data, not XML!
Solution Options
Option 1: Modify the entire application to switch from XML to JSON
❌ Time-consuming and labor-intensive
❌ May break currently working code
Option 2: Modify the third-party library to understand XML
❌ You don't have access to the source code
❌ Every time the library updates, you'll need to modify it again
Option 3: Use Adapter Pattern ✅
Create Adapter classes to convert from XML to JSON
Current code doesn't need to change
Third-party library remains intact
When Adapter receives a request from the client, it will:
1. Receive XML data
2. Convert to JSON
3. Call the appropriate method of the library
4. Return results to the client
Adapter Pattern has two main implementation approaches:
a. Object Adapter (Composition) - Most Common
This approach uses Composition (contains object inside).
Components:
1. Client: Class containing the main business logic of the application
2. Target Interface (Client Interface): The interface that Client expects and can work with
3. Service (Adaptee): Useful class but with incompatible interface (usually third-party library)
4. Adapter: The intermediary class that performs the "magic":
- Implements Target Interface that Client needs
- Contains an instance of Service inside
- When Client calls method through Target Interface, Adapter converts and calls corresponding Service method
Illustration:
Client → call → IShape.Draw(x1, y1, x2, y2)
↓
Adapter → converts → LegacyRectangle.Draw(x, y, width, height)This approach uses Inheritance.
Characteristics:
Adapter inherits from Service (Adaptee)
Simultaneously implements Target Interface
Doesn't need to contain Service object since it inherits methods from it
Uses override to perform conversion
CriteriaObject AdapterClass AdapterConnection MethodComposition (contains object)InheritanceFlexibilityHigh - can work with multiple ServicesLow - only works with one specific classLanguage SupportAny OOP languageRequires language supporting multiple inheritanceRecommendation✅ Should use⚠️ Use only when truly necessaryWhy is Object Adapter Better?
More flexible: one Adapter can work with multiple different Adaptees
If Adaptee is a concrete class, Class Adapter cannot serve its other subclasses
✅ Single Responsibility Principle
Separates data conversion logic from main business logic
Code is easier to maintain and extend
✅ Open/Closed Principle
Add new adapters without affecting existing code
Code is not affected when third-party API changes (function names, class names, structure...)
✅ Reusability
Reuse existing classes without modification
Integrate new libraries easily
❌ Increased Complexity
Must create additional interfaces and classes
Have an extra intermediary layer
❌ Sometimes Overkill
If there are only a few small incompatibilities, modifying Service directly might be simpler
✅ Use when:
Third-party library integration: You want to use an existing library but the interface doesn't match your current code
Legacy code: You have a legacy system that needs to integrate with a new system without modifying old code
Subclass reuse: You want to reuse multiple subclasses missing common functionality, but cannot add to parent class
Multi-system environment: New components need to integrate and work with existing components
❌ Don't use when:
You can easily modify Service to match desired interface
System is too simple, adding Adapter creates unnecessary complexity
You have legacy drawing classes using different parameter inputs:
LegacyLine: draws straight line with 4 parameters (x1, y1, x2, y2)
LegacyRectangle: draws rectangle with 4 parameters (x, y, width, height)
But your new system needs all shapes to implement IShape interface with method Draw(x1, y1, x2, y2).
csharp
// Target Interface - Interface that Client expects
internal interface IShape
{
void Draw(int x1, int y1, int x2, int y2);
}
// Service (Adaptee) - Old incompatible class
internal class LegacyLine
{
internal void Draw(int x1, int y1, int x2, int y2)
{
Console.WriteLine($"Drawing line from ({x1},{y1}) to ({x2},{y2})");
}
}
internal class LegacyRectangle
{
internal void Draw(int x, int y, int w, int h)
{
Console.WriteLine($"Drawing rectangle at ({x},{y}) with width={w}, height={h}");
}
}
// Adapter for Line - simple because interface already matches
internal class LineAdapter : IShape
{
private readonly LegacyLine _legacyLine;
public LineAdapter(LegacyLine legacyLine)
{
_legacyLine = legacyLine;
}
public void Draw(int x1, int y1, int x2, int y2)
{
// No conversion needed, call directly
_legacyLine.Draw(x1, y1, x2, y2);
}
}
// Adapter for Rectangle - more complex because parameters need conversion
internal class RectangleAdapter : IShape
{
private readonly LegacyRectangle _legacyRectangle;
public RectangleAdapter(LegacyRectangle legacyRectangle)
{
_legacyRectangle = legacyRectangle;
}
public void Draw(int x1, int y1, int x2, int y2)
{
// Convert from (x1,y1,x2,y2) to (x,y,width,height)
int x = Math.Min(x1, x2); // Get minimum x coordinate
int y = Math.Min(y1, y2); // Get minimum y coordinate
int w = Math.Abs(x2 - x1); // Calculate width
int h = Math.Abs(y2 - y1); // Calculate height
_legacyRectangle.Draw(x, y, w, h);
}
}
// Client
class Program
{
static void Main(string[] args)
{
// Client only works with IShape, doesn't need to know internal details
List<IShape> shapes = new List<IShape>()
{
new LineAdapter(new LegacyLine()),
new RectangleAdapter(new LegacyRectangle())
};
int x1 = 5, y1 = 10, x2 = -3, y2 = 2;
// Draw all shapes using the same interface
shapes.ForEach(shape => shape.Draw(x1, y1, x2, y2));
// Output:
// Drawing line from (5,10) to (-3,2)
// Drawing rectangle at (-3,2) with width=8, height=8
}
}IShape Interface: Defines the contract all shapes must follow
Legacy classes: Old classes with incompatible interfaces
Adapters: Convert from old interface to new interface
Client: Works purely with IShape, doesn't care about implementation details
Benefits:
Adding new shapes doesn't require modifying old code
Legacy code continues working normally
Client code is clean and easy to test
Bridge Pattern
Similar: Structure quite similar to Adapter
Different: Different purpose - Bridge separates interface from implementation from the start for easy independent changes
When to use: When designing new system and want to separate abstraction from implementation
Decorator Pattern
Similar: Also wraps an object
Different: Decorator adds new functionality without changing interface; Adapter changes interface
When to use: When wanting to add dynamic functionality to object
Proxy Pattern
Similar: Also acts as intermediary
Different: Proxy keeps the same interface; Adapter changes interface
When to use: When needing access control, lazy loading, or caching
Adapter Pattern is an extremely useful design pattern in practice, especially when:
Integrating systems with third-party libraries
Working with legacy code
Wanting to reuse code without modification
Remember: Adapter is like a bridge between incompatible things. It doesn't change the nature of objects, but helps them "talk" to each other!
If you found this article helpful, explore more in the Design Patterns Series to enhance your programming skills!
[1] Refactoring.Guru. https://refactoring.guru/design-patterns
[2] Design Patterns for Dummies, Steve Holzner, PhD
[3] Head First Design Patterns, Eric Freeman
[4] Gang of Four Design Patterns 4.0
[5] Dive into Design Patterns