How to Use Adapter Design Pattern to Have Flexible C# Code


Let’s say you have a library that you need to include in your project. But here is the problem: its interface is incompatible with the rest of the code. So how can you change its interface to match the rest of the code?

Use the Adapter pattern. It wraps the incompatible component in a compatible interface, so you can use it in your code.

The Adapter design pattern is a software engineering technique that allows two software components to work together even if they have incompatible interfaces. It is a type of structural design pattern that helps define the relationships between objects. It is also known as the Wrapper pattern.

This article will teach you about the Adapter Design pattern with a real-life implementation in C#. Furthermore, you will also learn how to make every C# Adapter design pattern successful with three simple tests.

So, stay tuned and keep learning!

What is the Adapter design pattern?

Design patterns are the fundamental part of any software design built to bring ease to its user’s life. They play a vital role in the software development process and being a software developer, you must know multiple design patterns. The Gang of Four (The founders of design patterns) proposed 23 design patterns, each with different purposes and applications.

The Adapter design pattern, like any other design pattern, is used to resolve a recurring programming problem by making the collaboration possible between the incompatible objects.

In daily life, you use an adapter to connect devices that cannot connect directly.

Similarly, you can use the Adapter design pattern to connect the incompatible objects that cannot connect directly. It makes different classes work together without modifying the existing code. It is the structural design pattern. You can think of it as a wrapper that catches the call for one object and transforms the request in a way understandable by the second object. Simply put, the Adapter design pattern can be considered an interface that makes the communication between two incompatible objects possible.

The Adapter design patterns can come in two variations:

  • Object Adapter pattern
  • Class Adapter pattern

Object Adapter

The Adapter in this pattern implements the interface of one object and wraps the other. Implementable by all popular programming languages, it uses composition to implement the functionality.

Class Adapter

The Class Adapter pattern works the same as Object Adapter, except that it handles the incompatibility by inheritance. The Adapter in this pattern inherits the interfaces of both objects. Only the programming languages that support multiple inheritances (for example, C++) can implement it.

Conceptual example

Suppose that you go to a country whose dialect is unknown to you. On the other hand, the people of that country do not understand your dialect either. How would you tackle such a situation? You guessed it right! You will hire a translator who knows their as well as your dialect. The translator will then translate your words into their language and vice versa. Similarly, the Adapter pattern makes communication between two unknown and incompatible classes possible by transforming the called interface per caller requirements. 

When to use the Adapter design pattern?

  • You can use the Adapter pattern when you want to reuse an existing class, but its interface is incompatible with the rest of the code.
  • It lets you create a class that is a translator between your code and any other class with an unknown interface.
  • You should use this pattern when several existing subclasses are reused but lack some standard functionality not added to the superclass. 

How to use the Adapter pattern effectively?

To use the Adapter pattern effectively, you must ensure that your Adapter fulfills the following requirements.

#1 The Adapter must mediate between two interfaces

This point requires you to design the adapters that can understand and are compatible with both interfaces you are trying to connect. In simple words, the Adapter must be able to communicate with interface_A and interface_B to make communication possible between them. Therefore, it will have no use if an adapter is compatible with interface_A but not with interface_B or vice versa. 

#2 It must be easily configurable

The Adapter must be easily configurable to be more usable. If an adapter is not configurable, you will not be able to reuse it in different situations, and you will have to define a new adapter whenever you need an interface between two incompatible objects.

#3 It must be robust and highly testable

An adapter must be robust enough to handle the complex object calls and be highly testable in different circumstances.

Who Are the participants in the Adapter design pattern?

The following class UML diagram depicts the implementation of the Adapter design pattern.

Adapter pattern - UML class diagram

The participants in this design pattern are:

  • Client – The client class interacts with the adapter class. Only the Target interface is visible to the Client.
  • Target – Target is the desired interface that the Client will access.
  • Adapter – This is the interface between two incompatible classes. This wrapper class implements the Target interface and adapts the Adaptee interface for the Client.
  • Adaptee – Adaptee refers to the interface that needs to be adapted.

The Client requests to access the Target interface. Therefore, the Adapter class that implements the Target interface will indirectly help the Client interact with the Adaptee interface. This way, the Adapter pattern helps the two incompatible interfaces communicate with each other without directly interacting.

Step by step on how to implement the Adapter design pattern

  • Step 1 – Identify two classes implementing the incompatible interfaces.
  • Step 2 – Declare the client class and define how it will communicate with the service.
  • Step 3 – Create the Adapter and make it follow the client interface leaving all the methods empty.
  • Step 4 – Initialize a field in the adapter class with reference to the service object. The service object is an adaptee. It can be a 3rd party class or a legacy class.
  • Step 5 – Implement the methods of the Client’s interface in the adapter class. Make sure that the Adapter only delegates the real work of the service object.
  • Step 6 – Make the Client use the Adapter class via the Client interface so that you can change the adapters without modifying the Client’s code.

Adapter design pattern – Sample code in C#

The following example shows the structural code of the Adapter pattern in C#:

//Declaring the Target that the Client will use
public interface ITarget
{
    public void MethodA();
}

//Defining the Adaptee to be accessed by the Client
public class Adaptee
{
    public void MethodB()
    {
        Console.WriteLine("Method from Adaptee class.");
    }
}

//Defining the Adapter that implements the Target interface
public class Adapter: ITarget
{
    private Adaptee _adaptee;

    public Adapter(Adaptee adaptee)
    {
        _adaptee = adaptee;
    }

    public void MethodA()
    {
        _adaptee.MethodB();  //Calling Adaptee method via Adapter
    }
}

//Defining Client who will request the access to Adaptee
public class Client
{
    private ITarget target;
    public Client(ITarget target)
    {
        this.target = target;
    }
    public void Request()
    {
        target.MethodA();
    }
}

And the usage.

ITarget target = new Adapter(new Adaptee());
Client client = new Client(target);
client.Request();

It produces the following output.

Method from Adaptee class.

Adapter pattern – Real-world example in C#

adapter pattern - real-world example C#

You can find many real-world implementations of the Adapter design pattern in different programming languages. In this example, a billing system needs access to the Employees class but cannot directly call it. So, to fulfill the requirements of the billing system, you will define an interface and implement it through the Adapter to indirectly call the Employees class. Later, the billing system will use that interface to access the Employees class. 

Here is the coding example.

//Declaring the Target that the Client will use
public interface IEmployeeTarget
{
    string EmployeeList();
}

//Defining the Adaptee to be accessed by the Client
public class Employees
{
    public string GetEmployee()
    {
        return "John Francis, Manager";
    }

}

//Defining the Adapter that implements the Target interface
public class EmployeeAdapter: IEmployeeTarget
{
    private Employees _employees;

    public EmployeeAdapter(Employees employees)
    {
        _employees = employees;
    }

    public string EmployeeList()
    {
        return _employees.GetEmployee();
    }
}

//Defining Client who will request the access to Adaptee
public class Billing
{
    private IEmployeeTarget _employeeTarget;
    public Billing(IEmployeeTarget employee_target)
    {
        _employeeTarget = employee_target;
    }
    public void ShowEmployees()
    {
        string employeesInfo = _employeeTarget.EmployeeList();
        Console.WriteLine("Employee:" + employeesInfo);
    }
}

In this example:

  • IEmployeeTarget is the target interface.
  • The Employee is the Adaptee class.
  • EmployeeAdapter is the Adapter class.
  • Billing is the Client that needs to access the Adaptee class.

The usage:

//Initializing the Target's object via Adapter
IEmployeeTarget target = new EmployeeAdapter(new Employees());
Billing billing = new Billing(target);
billing.ShowEmployees();

Output:

Employee:John Francis, Manager

Advantages of using the Adapter pattern

Out of the many advantages of the Adapter pattern, some are as follows:

  • It lets you make incompatible objects work together. This way, you do not have to implement the same functionality multiple times. With the help of the Adapter, you can implement the functionality once and reuse it several times.
  • You keep the data and business logic separate.
  • You can define new types of adapters without affecting the client code as long as they work with the client interface.
  • Promotes Open/Close principle.
  • Highly testable via mocking.

Disadvantages of the Adapter pattern

The significant disadvantages of the Adapter pattern are:

  • The code becomes complex due to the declaration of new classes and interfaces to implement the existing functionality.
  • The Client and Adaptee classes have additional request forwarding overloading on the System.

What are the related patterns to the Adapter design pattern?

The Adapter pattern relates to many design patterns on a different basis. The major related patterns are Bridge, Decorator, and Proxy patterns.

The Bridge pattern lets you design from scratch, whereas the Adapter implements the predefined functionality. The Decorator pattern enhances the object without changing its interface, and the Adapter changes the object’s interface. Finally, the Adapter pattern provides a separate interface to the object, whereas the Proxy pattern uses the same interface.

What is the difference between the Bridge pattern and the Adapter design pattern?

The following table shows the difference between the Bridge and Adapter patterns.

BridgeAdapter
It lets you develop the parts of the application independent of each other.Only used to connect existing incompatible objects.
Implemented before designing the application.Implemented after designing the application.
Serves to decouple the Abstraction class from its implementation.Converts interface between classes with less inheritance.

What is the difference between the Decorator pattern and the Adapter pattern?

The following table shows the difference between the Decorator and Adapter patterns.

DecoratorAdapter
Enhances an object without changing the interfaceEnhances an object by changing its interface.
Supports recursive composition.Does not support recursive composition.

In case you want to learn more about the Decorator pattern, and why it’s still highly relevant in modern .NET applications, check out this article.

What is the difference between the Adapter and Proxy Patterns?

The following table shows the difference between the Proxy and Adapter patterns.

ProxyAdapter
The proxy uses the same interface for the wrapped objects.It provides a different interface to the wrapped objects.
Has custom business logic for accessing the original object.Has only the transformation logic for accessing the original object.

The Proxy design pattern can have many useful usages in your code. The article about it outlines the 6 most common usages.

What is a Two-way Adapter?

An adapter that implements both interfaces of Target and Adaptee is called a Two-way Adapter. The Two-way Adapter allows you to use the adapted object as Target when the new System deals with Target classes and Adaptee when the System deals with the Adaptee class.

Simply put, you can say that a Two-way adapter implements n interfaces, adapting to n systems. The two-way adapters are challenging to implement in the systems that do not support multiple inheritances.

Conclusion

The Adapter design pattern helps you connect two or more incompatible interfaces to work together. This design pattern is a structural design pattern proposed by the Gang of Four. It enhances the existing objects by changing their interfaces. It is related to Bridge, Decorator, and Proxy design patterns.

This article discusses the Adapter design pattern with the structural code and a real-world example.

If you want to learn more about design patterns, check the separate in-depth article about design patterns in C#.

Recent Posts