Adapter structural design pattern

 A design pattern is a general solution to a recurring problem in software design. It is a proven approach to solving a specific type of problem that arises frequently in software development. Design patterns provide a shared vocabulary and a set of well-defined steps for solving common problems, making them a valuable tool for software engineers to use when building applications.


Adapter Pattern - It allows the interface of an existing class to be used by another interface.

// The Target interface specifies the client's required interface.

  1. interface ITarget
  2. {
  3. void Request();
  4. }

// The Adaptee contains some useful behavior, but its interface is incompatible with the existing client code.

  1. class Adaptee
  2. {
  3.     public void SpecificRequest()
  4.     {
  5.         Console.WriteLine("Adaptee specific request.");
  6.     }
  7. }


// The Adapter makes the Adaptee's interface compatible with the Target's interface.

  1. class Adapter : ITarget
  2. {
  3.     private readonly Adaptee _adaptee;

  4.     public Adapter(Adaptee adaptee)
  5.     {
  6.         _adaptee = adaptee;
  7.     }

  8.     public void Request()
  9.     {
  10.         _adaptee.SpecificRequest();
  11.     }
  12. }


// The client code supports all classes that follow the Target interface.

  1. class Client
  2. {
  3.     static void Main()
  4.     {
  5.         Adaptee adaptee = new Adaptee();
  6.         ITarget target = new Adapter(adaptee);
  7.         target.Request();
  8.     }
  9. }



In this example, the Adaptee class has a SpecificRequest() method that is incompatible with the ITarget interface that the client code expects. To make the two interfaces compatible, an Adapter class is created that implements the ITarget interface and internally uses an instance of Adaptee to perform the required behavior. Finally, the Client code uses the Adapter class to call the Request() method which internally calls the SpecificRequest() method of Adaptee.



Real-world useful examples


Database Adapters - Many applications use different types of databases, and each database has its own interface for accessing and manipulating data. A database adapter can be used to provide a common interface for accessing different types of databases.


Audio/Video Adapters - In the world of multimedia, different devices use different connectors, and each connector has its own protocol for transmitting data. Audio/video adapters can be used to convert signals from one format to another, making it possible to connect different devices and play media files.


Third-party library integration - When using a third-party library in your application, it may not have the same interface as the rest of your codebase. In this case, an adapter can be created to provide a bridge between the two interfaces, allowing the library to be used in the application.


Legacy system integration - In many cases, legacy systems that were built using old technologies may need to be integrated into new systems. An adapter can be used to convert the data and functionality of the legacy system into a format that can be used by the new system.


Example 

Suppose you have a legacy system that has a LegacyCalculator class with a Calculate() method that returns the result of a complex calculation. The method takes two double values as input and returns a double result. You want to use this legacy system in your new C# application, but the interface of the LegacyCalculator is not compatible with the interface of the rest of your application. In this case, you can use the Adapter pattern to create an adapter class that makes the LegacyCalculator compatible with the rest of your codebase.


Here's the code for the LegacyCalculator class:


  1. public class LegacyCalculator
  2. {
  3.     public double Calculate(double x, double y)
  4.     {
  5.         // Complex calculation logic goes here
  6.         double result = x + y;
  7.         return result;
  8.     }
  9. }



And here's the code for the ITarget interface:


  1. public interface ITarget
  2. {
  3.     double Add(double x, double y);
  4. }

Now, we can create the Adapter class:


  1. public class LegacyCalculatorAdapter : ITarget
  2. {
  3.     private readonly LegacyCalculator _legacyCalculator;

  1.     public LegacyCalculatorAdapter(LegacyCalculator legacyCalculator)
  2.     {
  3.         _legacyCalculator = legacyCalculator;
  4.     }

  5.     public double Add(double x, double y)
  6.     {
  7.         return _legacyCalculator.Calculate(x, y);
  8.     }
  9. }


In the LegacyCalculatorAdapter class, we implement the ITarget interface and internally use an instance of LegacyCalculator to perform the required calculation. The Add() method of the adapter class calls the Calculate() method of the LegacyCalculator class to perform the calculation and return the result.


Finally, we can use the LegacyCalculatorAdapter class in our client code like this:


  1. class Client
  2. {
  3.     static void Main()
  4.     {
  5.         LegacyCalculator legacyCalculator = new LegacyCalculator();
  6.         ITarget target = new LegacyCalculatorAdapter(legacyCalculator);
  7.         double result = target.Add(2, 3); // Calls the Calculate() method of the LegacyCalculator
  8.         Console.WriteLine($"The result is {result}");
  9.     }
  10. }



This way, the client code can use the legacy system in a way that is compatible with the rest of the codebase, without needing to modify the legacy system itself.



Adapter vs Decorator design pattern 


While the Adapter and Decorator patterns share some similarities, they serve different purposes.


The Adapter pattern is used to convert the interface of an existing class into another interface that the client code expects. The Adapter allows classes with incompatible interfaces to work together by wrapping an object with a new interface. It doesn't change the behavior of the original class; it simply provides a new interface for it.


The Decorator pattern, on the other hand, adds new behavior to an existing object dynamically. It allows the addition of new functionality to an object without changing its original structure or behavior. Decorators wrap objects with other objects that provide additional features and functionalities. Decorators provide a flexible alternative to subclassing for extending functionality.


In summary, while the Adapter pattern is used to make two incompatible interfaces work together, the Decorator pattern is used to add new functionality to an object without changing its original structure or behavior













Comments

Post a Comment