Adapter Design Pattern in C# with Example

1. Definition

The Adapter Pattern allows two incompatible interfaces to work together. It acts as a bridge between two interfaces by converting one interface of a class into another interface that a client expects. It lets you wrap an otherwise incompatible object in an adapter to make it compatible with another class.

2. Problem Statement

Imagine you have a system that expects a specific interface to process information. However, you now have a new component with a different interface that provides the data you need. Directly integrating this new component will require changing the existing system, which may introduce bugs or unwanted behaviors.

3. Solution

Instead of modifying the existing system, you can create an adapter class that converts the new component's interface into the one your system expects. The existing system interacts with the adapter, and the adapter, in turn, interacts with the new component.

4. Real-World Use Cases

1. Integrating new libraries that have different interfaces from what your system expects.

2. Legacy code integration where you want to avoid changing the original code but need to use its functionality with new systems.

3. Providing a unified interface for multiple sources of similar kinds of data.

5. Implementation Steps

1. Identify the target interface that your system expects.

2. Implement the adapter class, making it comply with the target interface.

3. In the adapter, implement the necessary translations or transformations that allow interaction with the adaptee.

6. Implementation in C#

// Step 1: The target interface
public interface ITarget
{
    string GetRequest();
}

// The Adaptee contains some useful behavior, but its interface is incompatible with the existing system.
public class Adaptee
{
    public string GetSpecificRequest()
    {
        return "Specific request from Adaptee.";
    }
}

// Step 2: The Adapter class
public class Adapter : ITarget
{
    private readonly Adaptee _adaptee;

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

    public string GetRequest()
    {
        return $"Adapter: (TRANSLATED) {_adaptee.GetSpecificRequest()}";
    }
}

public class Program
{
    public static void Main()
    {
        Adaptee adaptee = new Adaptee();
        ITarget adapter = new Adapter(adaptee);

        Console.WriteLine(adapter.GetRequest());
    }
}

Output:

Adapter: (TRANSLATED) Specific request from Adaptee.

Explanation:

In the example, we have an existing Adaptee class with a method named GetSpecificRequest(). The system we are integrating with, however, expects any object it interacts with to implement the ITarget interface and its GetRequest() method.

To bridge the gap, we introduce the Adapter class that implements ITarget and composes an instance of the Adaptee. The GetRequest() method of the adapter then calls the GetSpecificRequest() of the Adaptee, essentially translating the request and making the two interfaces compatible.

7. When to use?

The Adapter pattern is useful when:

1. You want to use an existing class, but its interface isn't compatible with the rest of your application.

2. You need to integrate with systems or libraries that have different interfaces.

3. You want to create a reusable class that works with possibly unforeseen classes and interfaces.


Comments