Adapter

Adapter

This is the 6th article of the design pattern series and the first article of the structural design pattern so let’s talk a bit about structural design patterns and then we will dive into Adapter design pattern.

What are structural design patterns?

Structural design patterns are concerned with how classes and objects are composed to form larger structures. They focus on simplifying the design by identifying simple ways to realize relationships between entities.

Why do we need structural design patterns?

  1. Encapsulation: Structural patterns promote encapsulation by ensuring that changes to the structure of classes and objects do not affect other parts of the system. This makes the code more modular and easier to maintain.
  2. Flexibility: They provide flexibility by allowing classes and objects to be composed in different ways to achieve varying behaviors. This makes the system adaptable to changing requirements.
  3. Reusability: Structural patterns promote code reuse by defining common ways to assemble classes and objects. This reduces duplication and promotes consistency across the codebase.
  4. Abstraction: They help in managing complexity by abstracting away the details of how objects are composed. This allows developers to focus on high-level design decisions rather than low-level implementation details.
  5. Performance: Structural patterns can improve performance by optimizing the composition of classes and objects. They can reduce memory consumption, improve runtime efficiency, and minimize overhead.

List of Structural Design Patterns

Here are some common structural design patterns along with brief descriptions for each one:

  1. Adapter Pattern: Allows objects with incompatible interfaces to work together by providing a wrapper that translates one interface to another.
  2. Bridge Pattern: Separates an abstraction from its implementation, allowing them to vary independently. It provides a bridge between the abstraction and its implementation.
  3. Composite Pattern: Allows clients to treat individual objects and compositions of objects uniformly. It represents part-whole hierarchies of objects.
  4. Decorator Pattern: Dynamically adds responsibilities to objects by wrapping them with one or more decorator objects. It provides a flexible alternative to subclassing for extending functionality.
  5. Facade Pattern: Provides a simplified interface to a complex subsystem, hiding its complexity from clients. It acts as a single entry point to a group of interfaces in a subsystem.
  6. Flyweight Pattern: Shares common state between multiple objects to conserve memory and improve performance. It separates intrinsic and extrinsic state to minimize memory usage.
  7. Proxy Pattern: Provides a surrogate or placeholder for another object to control access to it. It allows for additional functionality to be provided when accessing an object.

To remember the list of structural design patterns you need to remember first letter of them is this order (ABCD FF P)

1. Overview of the Adapter Pattern

The Adapter Design Pattern is a structural pattern that allows incompatible interfaces to work together. It acts as a bridge between two incompatible interfaces by providing a wrapper that translates one interface to another. It involves creating an adapter class that implements the target interface and delegates the calls to the adaptee object, which has a different interface. This enables objects with incompatible interfaces to work together seamlessly.

I created a solution in my GitHub that contains a project called Adapter for this article. You can find it here:

https://github.com/amirdoosti6060/DesignPatterns

2. Structure of the Adapter Pattern

  • Target Interface: Defines the interface that the client expects.
  • Adaptee: Defines the interface that needs to be adapted.
  • Adapter: Implements the target interface and delegates the calls to the adaptee.

Article content

3. Implementation in C#

Let's demonstrate the Adapter pattern with a simple example of adapting a legacy class to work with a modern interface:

// Target Interface
public interface IModernTarget
{
    string[] Request();
}

// Adaptee
public class LegacyAdaptee
{
    public void SpecificRequest()
    {
        return "item1,item2,item3";
    }
}

// Adapter
public class Adapter : IModernTarget
{
    private readonly LegacyAdaptee _legacyAdaptee;

    public Adapter(LegacyAdaptee legacyAdaptee)
    {
        _legacyAdaptee = legacyAdaptee;
    }

    public string[] Request()
    {
        // Call the legacy method and adapt the interface
        return _legacyAdaptee.SpecificRequest().Split(new char[] {','});
    }
}        

In this example our modern class needs to return string array in Request but legacy adapter return a comma separated string which doesn't match to what we need so we used an Adapter to solve this problem.

4. Usage of the Adapter Pattern

class Program
{
    static void Main(string[] args)
    {
        // Create the legacy adaptee
        var legacyAdaptee = new LegacyAdaptee();

        // Create the adapter
        var adapter = new Adapter(legacyAdaptee);

        // Use the adapter through the modern interface
        string[] result = adapter.Request();
    }
}        

As you may noticed, we passed legacy adaptee to the Adapter and just call its Request method to get what we need.

5. Benefits of the Adapter Pattern

  • Compatibility: Enables collaboration between classes with incompatible interfaces.
  • Reusability: Allows existing classes to be reused even if their interfaces don't match the client's expectations.
  • Separation of Concerns: Promotes separation between client code and legacy code by encapsulating the adaptation logic within the adapter.

6. When to Use the Adapter Pattern

  • Use the Adapter pattern when you need to integrate existing classes or libraries with incompatible interfaces into your application.
  • Use it when you want to reuse existing classes without modifying their interfaces.

Existing Adapter in .NET

Here are a few examples of existing adapters in .NET along with explanations of why they can be considered instances of the Adapter pattern:

1. StreamReader and StreamWriter in System.IO namespace

  • Explanation: The StreamReader and StreamWriter classes in the System.IO namespace provide a convenient way to read from and write to streams in .NET. They serve as adapters between the stream-based interfaces (e.g., Stream) and the higher-level interfaces for reading and writing text (e.g., TextReader and TextWriter). By wrapping a stream with a StreamReader or StreamWriter, you can easily read and write text data to and from the stream, adapting the stream-based interface to a text-based interface.

2. ArrayAdapter class in System.Collections namespace

  • Explanation: The ArrayAdapter class in the System.Collections namespace provides an adapter between arrays and the IList interface. It allows you to use arrays as if they were instances of the IList interface, which provides a more versatile and consistent way to work with collections of data. The ArrayAdapter class achieves this by implementing the IList interface and delegating the method calls to the underlying array, effectively adapting the array to the IList interface.

3. HttpRequestWrapper and HttpResponseWrapper classes in ASP.NET

  • Explanation: In ASP.NET, the HttpRequestWrapper and HttpResponseWrapper classes serve as adapters between the ASP.NET-specific HttpRequest and HttpResponse objects and the more general HttpContextBase interface. They allow ASP.NET-specific functionality to be accessed through the more abstract HttpContextBase interface, which provides a common way to work with HTTP requests and responses in ASP.NET applications. These wrapper classes adapt the ASP.NET-specific interfaces to the more generic HttpContextBase interface, enabling greater flexibility and compatibility with different ASP.NET frameworks and components.

Conclusion

In conclusion, the Adapter Design Pattern provides a straightforward solution for integrating classes with incompatible interfaces. By following the principles outlined in this article and using the provided example, you can leverage the Adapter pattern in your C# applications to achieve seamless integration between disparate components.


#designpattern #structuraldesignpattern #adapter #csharp #dotnet


To view or add a comment, sign in

More articles by Amir Doosti

  • Modern Thread Communication in C#

    Managing communication between threads is a fundamental challenge in modern software development. Historically…

  • Impersonation

    Impersonation in C# What is impersonation? Impersonation is a security mechanism that allows your code to execute under…

    2 Comments
  • Span<T> and Memory<T> in C#

    Modern .NET and C# put a huge emphasis on high performance with minimal allocations.

  • Network Programming in c# - Part 2 (HTTP programming)

    In the previous article I talked about Socket programming. Today, I’m going to cover another type of network…

  • Network Programming in C# - Part 1

    Network programming in C# involves using .NET libraries to communicate between systems over a network.

    2 Comments
  • Locking (Synchronization) in C#

    Concurrency and multithreading are powerful features in modern programming, but they bring challenges, especially in…

    6 Comments
  • Plotting in C# (Part 4 - ScottPlot)

    ScottPlot is an open-source, .NET-based charting library designed for creating high-performance, interactive plots in…

  • Plotting in C# (Part 3 - OxyPlot)

    OxyPlot is a lightweight, open-source plotting library designed specifically for .NET applications, supporting…

    2 Comments
  • Plotting in C#.Net (Part2 - LiveCharts2)

    LiveCharts is a versatile and modern charting library that supports a variety of charts and visualizations with smooth…

  • Plotting in C#.Net (Part 1 - General)

    Plotting is a crucial tool for data analysis, visualization, and communication. There are many reasons why we need to…

    2 Comments

Others also viewed

Explore content categories