DEV Community

Cover image for Learn Design Patterns: Understanding the Adapter Pattern for Compatibility
Sami Maachi
Sami Maachi

Posted on

Learn Design Patterns: Understanding the Adapter Pattern for Compatibility

Learn Design Patterns: Understanding the Adapter Pattern for Compatibility

The Adapter Pattern is a structural design pattern that allows objects with incompatible interfaces to work together seamlessly. It acts as a bridge, enabling communication between components that otherwise couldn’t interact.

In this article, we’ll delve into the Adapter Pattern, using a common and relatable example of sockets and plugs. You’ll learn its definition, purpose, real-world applications, and how to implement it in TypeScript.

Table of Contents

  1. Adapter Pattern Definition

  2. Scenario: Understanding the Problem

  3. Real-Life Projects That Use the Adapter Pattern

1.Adapter Pattern Definition

The Adapter Pattern allows classes with incompatible interfaces to collaborate by introducing an intermediary object, the adapter, which translates one interface into another. This pattern is handy when you want to use a class that doesn’t fit with the interface your application requires.

2.Scenario: Understanding the Problem

Imagine you’ve traveled from the United States to Europe. Your devices have a plug designed for the American socket standard, but European sockets have a different design. Without an adapter, you wouldn’t be able to use your devices.

The adapter acts as a mediator, translating the plug’s interface to be compatible with the socket. Similarly, in software, an adapter bridges the gap between incompatible interfaces, allowing systems to communicate effortlessly.

Implementation in TypeScript

Here’s how the Adapter Pattern can be implemented in TypeScript, inspired by the sockets and plugs analogy:

Step 1: Define the interfaces

    interface IPlug {
      getHeads: number;
      getIsHeadsRounded: boolean;
    }

    interface ISocket {
      getHoles: number;
      getIsHolesRounded: boolean;
      plugIn(plug: IPlug): boolean;
    }
Enter fullscreen mode Exit fullscreen mode

Step 2: Define the socket class (target)

    class EuropeanSocket implements ISocket {
      private holes: number = 2;
      private isHolesRounded: boolean = true;

      public get getHoles(): number {
        return this.holes;
      }

      public get getIsHolesRounded(): boolean {
        return this.isHolesRounded;
      }

      public plugIn(plug: IPlug): boolean {
        if (
          plug.getHeads === this.holes &&
          plug.getIsHeadsRounded === this.isHolesRounded
        )
          console.log("Plug entered with success");
        else console.log("Plug incompatible with the socket");
        return (
          plug.getHeads === this.holes &&
          plug.getIsHeadsRounded === this.isHolesRounded
        );
      }
    }
Enter fullscreen mode Exit fullscreen mode

Step 3: Define the plug class (adaptee)

    class AmericanPlug implements IPlug {
      private heads: number = 2;
      private isHeadsRounded: boolean = false;

      public get getHeads(): number {
        return this.heads;
      }

      public get getIsHeadsRounded(): boolean {
        return this.isHeadsRounded;
      }
    }
Enter fullscreen mode Exit fullscreen mode

Step 4: define the Adapter class

    class SocketAdapter implements IPlug {
      private americanPlug: AmericanPlug;

      constructor(americanPlug: AmericanPlug) {
        this.americanPlug = americanPlug;
      }

      public get getHeads(): number {
        return 2;
      }

      public get getIsHeadsRounded(): boolean {
        return true;
      }
    }
Enter fullscreen mode Exit fullscreen mode

Step 5: Use the adapter in the client code

    const americanPlug = new AmericanPlug();
    const europeanSocket = new EuropeanSocket();
    console.log('Enter the plug without adapter:');
    europeanSocket.plugIn(americanPlug);

    console.log('Enter the plug with the adapter:');
    const adapter = new SocketAdapter(americanPlug);
    europeanSocket.plugIn(adapter);
Enter fullscreen mode Exit fullscreen mode

Output :

Enter the plug without adapter:
Plug incompatible with the socket
Enter the plug with the adapter:
Plug entered with success
Enter fullscreen mode Exit fullscreen mode




3.Real-Life Projects That Use the Adapter Pattern

  1. Third-Party Integration: Connecting third-party APIs with an application that uses a different format.

  2. Legacy Code Integration: Allowing new systems to work seamlessly with legacy codebases.

  3. Cross-Platform Compatibility: Enabling systems designed for one platform to operate on another with minimal changes.

The Adapter Pattern exemplifies how software design can borrow concepts from real-life problems, offering elegant solutions for compatibility challenges. Let us know in the comments how you’ve used this pattern in your projects!

In the next article of our Learn Design Patterns series, we’ll explore the Bridge Pattern Stay tuned!

Top comments (0)