RO EN

Adapter Pattern – compatibility between classes with different interfaces

Adapter Pattern – compatibility between classes with different interfaces
Doru Bulubasa
17 November 2025

In software development, we often work with libraries, services, or components that were not designed to work together. Either they have different interfaces, or they expose methods incompatible with the application's needs. This is where the Adapter Pattern comes in, an essential structural design pattern that allows the integration of two otherwise incompatible classes without modifying their implementations.

If a Factory helps you create objects and a Builder helps you compose them, Adapter allows you to use existing objects in a new context. That is why it is one of the most used patterns in enterprise projects, API integrations, and refactorings.


🧩 What is Adapter Pattern?

Adapter is an intermediary object that transforms the interface of one class into another, expected by the client.

Thus, the client does not know and does not need to know that it is using a different object — everything is done transparently, through adaptation.

Purpose: to make two classes with incompatible interfaces collaborate.

This pattern frequently appears when:

  • you integrate an external library (which cannot be modified),

  • you convert different formats,

  • you refactor an old application to work with a new architecture,

  • you connect various data sources (API, XML, JSON, databases, microservices, etc.).


🔧 Applied example in .NET (C#)

Suppose you have a system that works with an interface:

public interface IEmailSender
{
    void SendEmail(string to, string message);
}

But you have an external library that exposes a different method:

public class ExternalMailService
{
    public void Send(string destination, string body)
    {
        Console.WriteLine($"Mail sent to {destination}: {body}");
    }
}

The two are not compatible. However, you want to use the external library in your system.

Here comes the Adapter:

public class EmailSenderAdapter : IEmailSender
{
    private readonly ExternalMailService _externalService;

    public EmailSenderAdapter(ExternalMailService service)
    {
        _externalService = service;
    }

    public void SendEmail(string to, string message)
    {
        _externalService.Send(to, message); // simple adaptation
    }
}

Now the client uses the standard system:

IEmailSender emailSender = new EmailSenderAdapter(new ExternalMailService());
emailSender.SendEmail("test@example.com", "Hello!");

Without knowing that something else is used behind the scenes — exactly the essence of the Adapter Pattern.


🏗 Types of Adapter

There are two main variants:

1. Class Adapter (based on inheritance)

Uses inheritance. Less commonly used in C# because C# does not allow multiple inheritance.

It can only be encountered if you adapt a single concrete class.

2. Object Adapter (based on composition)

The most used in .NET.

The adapter contains the instance of the adapted object and uses it internally.

Major advantage:

✔ works with any implementation, as long as it is provided in the constructor.


🧠 Advantages of Adapter Pattern

  • Allows reuse of existing code.

  • Avoids modifying classes that cannot be changed (external API, legacy).

  • Favors flexible architectures (SOLID, especially Open/Closed).

  • Eases the transition between old and new systems.


⚠️ Disadvantages

  • Introduces an additional level of abstraction.

  • Can complicate the structure if you have too many adaptations.

  • If you have to adapt complex functionality, it can become hard to maintain.


🏁 Conclusion

Adapter Pattern is one of the most practical and frequently used patterns in .NET.

Whenever you have two systems with different interfaces, you can use it to make integration simple, clear, and scalable, reducing the need to modify existing code.

In enterprise projects, adaptation is constant: old APIs, new microservices, external libraries. That is why Adapter remains an essential tool in any C# developer’s arsenal.

If you want to build clean, extensible, and easy-to-maintain code, this pattern deserves to become a habit.