The Adapter is an object that translates one interface into another that the client understands. It allows integrating an existing class without modifying it or contaminating the rest of the system with its peculiarities.
Imagine you travel to the United States with a laptop bought in Spain. The charger has two round prongs, while the hotel socket expects flat prongs.
What do you do? Do you dismantle the hotel wall to change the socket? No. Do you cut your charger cable to splice on an American one? Hopefully not.
What you do is use an Adapter. An intermediate piece that receives your round prongs and outputs flat prongs. The laptop doesn’t know it’s in the US, and the wall doesn’t know a European device has been plugged in.
In software, the Adapter pattern (also known as Wrapper) does exactly the same: it allows objects with incompatible interfaces to collaborate.
The Problem: The Legacy System
This scenario is a classic one. We are building a modern application that consumes data in JSON format.
We have defined an interface that our system understands:
public interface INotifier
{
void SendMessage(string json);
}Everything goes well until we are told we have to integrate a third-party library (or an old company system) to send SMS. But that library is old and only understands XML.
// Third-party class (We cannot modify it)
public class OldSMSService
{
public void SendSMS_XML(string xmlData)
{
Console.WriteLine($"Sending XML: {xmlData}");
}
}We have a problem: our system speaks JSON (SendMessage), but the external service expects XML (SendSMS_XML). They are incompatible. We cannot change the external library (we don’t have the code or it’s third-party) and we don’t want to clutter our modern code with XML logic.
The Solution: The Wrapper
We create an intermediate class, the Adapter. This class:
Implements our modern interface (INotifier).
Contains an instance of the old service (OldSMSService).
In its method, it translates data from JSON to XML and calls the old service.
:::
Implementation in C#
Let’s build that adapter to connect our JSON system with the XML service.
// The Adapter implements the interface that OUR system expects (INotifier)
public class SMSAdapter : INotifier
{
// We keep a reference to the incompatible object (Composition)
private readonly OldSMSService _oldService;
public SMSAdapter(OldSMSService oldService)
{
_oldService = oldService;
}
// We translate the call
public void SendMessage(string jsonMessage)
{
// 1. Convert the data (the adaptation itself)
string xmlMessage = ConvertJsonToXml(jsonMessage);
// 2. Call the legacy service
Console.WriteLine("Adapter: Converting data and delegating...");
_oldService.SendSMS_XML(xmlMessage);
}
private string ConvertJsonToXml(string json)
{
// Conversion logic (simulated)
return $"<msg>{json}</msg>";
}
}Notice that the client has no idea that XML or old libraries exist. It only sees INotifier.
public class Client
{
public void ProcessNotifications(INotifier notifier)
{
string myJson = "{ 'text': 'Hello World' }";
notifier.SendMessage(myJson);
}
}class Program
{
static void Main(string[] args)
{
// We have the incompatible service
OldSMSService oldService = new OldSMSService();
// We create the adapter wrapping the old service
INotifier adapter = new SMSAdapter(oldService);
// The client uses the adapter as if it were any other notifier
Client client = new Client();
client.ProcessNotifications(adapter);
}
}Output:
Adapter: Converting data and delegating...
Sending XML: <msg>{ 'text': 'Hello World' }</msg>Types of Adapters
There are two variants of this pattern, although in C# we almost always use the first one:
- Object Adapter (The one we have seen): Uses Composition. The adapter contains an instance of the class to be adapted. It is the most flexible and the recommended one.
- Class Adapter: Uses Inheritance. The adapter inherits from both the Target and the Adaptee.
- Problem: C# (and Java) does not support multiple class inheritance. It could only be done if
Targetwere an interface (which it is) andAdapteea class. Even so, it couples the code more.
Whenever possible, prefer Composition over Inheritance. The Object Adapter is safer and allows adapting subclasses of the Adaptee if necessary.
When to use Adapter?
- Legacy Integration: When you want to use an existing class but its interface doesn’t match the rest of your code.
- Third-Party Libraries: To avoid coupling your domain to an external library. If tomorrow you change the SMS library for another one, you only need to create a new Adapter, without touching the rest of the code.
- Unification: When you have several classes with similar functionalities but different interfaces, and you want to process them polymorphically.