Adapter Pattern

The Adapter Pattern in Java is a structural design pattern that allows incompatible interfaces to work together. It acts as a bridge between two incompatible interfaces by providing a wrapper or adapter class that converts the interface of a class into another interface that a client expects.

Key Components

  1. Target: The interface that the client expects or uses.
  2. Adaptee: The existing class with an incompatible interface that needs to be adapted.
  3. Adapter: A class that implements the Target interface and translates calls to the Adaptee’s interface.
  4. Client: The code that interacts with the Target interface.

Types of Adapter Pattern

  • Object Adapter: Uses composition to hold an instance of the Adaptee and delegate calls to it (more flexible).
  • Class Adapter: Uses inheritance, where the Adapter inherits from both the Target and Adaptee (less common, requires multiple inheritance, not supported in languages like Java).

How It Works

  • The client interacts with the Adapter through the Target interface.
  • The Adapter translates the client’s requests into calls to the Adaptee’s methods.
  • The Adaptee performs the actual work, unaware of the Adapter.

Sample Implementation(Object Adapter)

// Target Interface
interface MediaPlayer {
    void play(String audioType, String fileName);
}

// Adaptee (Incompatible Interface)
class AdvancedMediaPlayer {
    public void playVlc(String fileName) {
        System.out.println("Playing VLC file: " + fileName);
    }

    public void playMp4(String fileName) {
        System.out.println("Playing MP4 file: " + fileName);
    }
}

// Adapter
class MediaAdapter implements MediaPlayer {
    private AdvancedMediaPlayer advancedPlayer;

    public MediaAdapter(String audioType) {
        if (audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")) {
            advancedPlayer = new AdvancedMediaPlayer();
        }
    }

    @Override
    public void play(String audioType, String fileName) {
        if (audioType.equalsIgnoreCase("vlc")) {
            advancedPlayer.playVlc(fileName);
        } else if (audioType.equalsIgnoreCase("mp4")) {
            advancedPlayer.playMp4(fileName);
        } else {
            System.out.println("Invalid media type: " + audioType);
        }
    }
}

// Client
class AudioPlayer implements MediaPlayer {
    private MediaAdapter mediaAdapter;

    @Override
    public void play(String audioType, String fileName) {
        if (audioType.equalsIgnoreCase("mp3")) {
            System.out.println("Playing MP3 file: " + fileName);
        } else if (audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")) {
            mediaAdapter = new MediaAdapter(audioType);
            mediaAdapter.play(audioType, fileName);
        } else {
            System.out.println("Unsupported format: " + audioType);
        }
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        AudioPlayer player = new AudioPlayer();
        player.play("mp3", "song.mp3");
        player.play("vlc", "movie.vlc");
        player.play("mp4", "video.mp4");
        player.play("avi", "clip.avi");
    }
}Code language: JavaScript (javascript)

Sample Implementation(Class Adapter)

// Target Interface
interface MediaPlayer {
    void play(String audioType, String fileName);
}

// Adaptee (Incompatible Interface)
class AdvancedMediaPlayer {
    public void playVlc(String fileName) {
        System.out.println("Playing VLC file: " + fileName);
    }

    public void playMp4(String fileName) {
        System.out.println("Playing MP4 file: " + fileName);
    }
}

// Class Adapter (Extends Adaptee and Implements Target)
class MediaAdapter extends AdvancedMediaPlayer implements MediaPlayer {
    @Override
    public void play(String audioType, String fileName) {
        if (audioType.equalsIgnoreCase("vlc")) {
            playVlc(fileName); // Call inherited method
        } else if (audioType.equalsIgnoreCase("mp4")) {
            playMp4(fileName); // Call inherited method
        } else {
            System.out.println("Invalid media type: " + audioType);
        }
    }
}

// Client
class AudioPlayer implements MediaPlayer {
    private MediaAdapter mediaAdapter;

    @Override
    public void play(String audioType, String fileName) {
        if (audioType.equalsIgnoreCase("mp3")) {
            System.out.println("Playing MP3 file: " + fileName);
        } else if (audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")) {
            mediaAdapter = new MediaAdapter();
            mediaAdapter.play(audioType, fileName);
        } else {
            System.out.println("Unsupported format: " + audioType);
        }
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        AudioPlayer player = new AudioPlayer();
        player.play("mp3", "song.mp3");
        player.play("vlc", "movie.vlc");
        player.play("mp4", "video.mp4");
        player.play("avi", "clip.avi");
    }
}Code language: JavaScript (javascript)

Use case and Implementation

Integrating third-party payment gateways with the bank’s existing payment processing system.

To integrate third-party payment gateways with the bank’s existing payment processing system using the Adapter pattern in Java, we can proceed with the following implementation:

Scenario Explanation

Imagine the bank has an established payment processing system that expects a specific interface for processing payments. However, there are multiple third-party payment gateways that the bank wants to integrate, each with its own unique interface. The Adapter pattern will allow us to create adapters for each third-party payment gateway to conform to the bank’s payment processing interface seamlessly.

//PaymentClient.java
// Third-party payment gateway A
class ThirdPartyPaymentGatewayA {
    public void processPayment(double amount) {
        // Simulating third-party payment gateway A's payment processing logic
        System.out.println("Third-Party Payment Gateway A is processing payment of $" + amount);
    }
}

// Third-party payment gateway B
class ThirdPartyPaymentGatewayB {
    public void makePayment(double amount) {
        // Simulating third-party payment gateway B's payment processing logic
        System.out.println("Third-Party Payment Gateway B is processing payment of $" + amount);
    }
}

// Target interface 
interface PaymentProcessor {
    void pay(double amount);
}

// Bank's existing payment processor
class BankPaymentProcessor implements PaymentProcessor {
    @Override
    public void pay(double amount) {
        // Implementation of payment processing logic
        System.out.println("Bank's payment processor is processing payment of $" + amount);
    }
}

// Adapter for Third-Party Payment Gateway A
class PaymentGatewayAAdapter implements PaymentProcessor {
    private ThirdPartyPaymentGatewayA gatewayA;

    public PaymentGatewayAAdapter(ThirdPartyPaymentGatewayA gatewayA) {
        this.gatewayA = gatewayA;
    }

    @Override
    public void pay(double amount) {
        // Adapt the method call to third-party gateway A's API
        gatewayA.processPayment(amount);
    }
}

// Adapter for Third-Party Payment Gateway B
class PaymentGatewayBAdapter implements PaymentProcessor {
    private ThirdPartyPaymentGatewayB gatewayB;

    public PaymentGatewayBAdapter(ThirdPartyPaymentGatewayB gatewayB) {
        this.gatewayB = gatewayB;
    }

    @Override
    public void pay(double amount) {
        // Adapt the method call to third-party gateway B's API
        gatewayB.makePayment(amount);
    }
}

public class PaymentClient {
    public static void main(String[] args) {
        // Bank's existing payment processor
        PaymentProcessor bankProcessor = new BankPaymentProcessor();

        // Third-party payment gateways
        ThirdPartyPaymentGatewayA gatewayA = new ThirdPartyPaymentGatewayA();
        ThirdPartyPaymentGatewayB gatewayB = new ThirdPartyPaymentGatewayB();

        // Adapters for third-party payment gateways
        PaymentProcessor adapterA = new PaymentGatewayAAdapter(gatewayA);
        PaymentProcessor adapterB = new PaymentGatewayBAdapter(gatewayB);

        // Process payments through different adapters
        bankProcessor.pay(1000);  // Using bank's processor
        adapterA.pay(500);        // Using adapter for gateway A
        adapterB.pay(700);        // Using adapter for gateway B
    }
}

/*

C:\>javac PaymentClient.java

C:\>java PaymentClient
Bank's payment processor is processing payment of $1000.0
Third-Party Payment Gateway A is processing payment of $500.0
Third-Party Payment Gateway B is processing payment of $700.0

*/

Advantages

  • Reusability: Allows reuse of existing classes with incompatible interfaces.
  • Flexibility: The Adapter can add or modify behavior during translation.
  • Single Responsibility: Separates interface conversion from business logic.
  • Interoperability: Enables integration of legacy or third-party code.

Disadvantages

  • Complexity: Adds an extra layer of abstraction, increasing code complexity.
  • Overhead: May introduce slight performance overhead due to additional calls.
  • Maintenance: Changes to the Adaptee’s interface require updates to the Adapter.

When to Use

  • When you need to use an existing class, but its interface is incompatible with the client’s expectations.
  • When integrating third-party libraries or legacy code with a different interface.
  • When you want to create a reusable class that cooperates with unrelated or unforeseen classes.

Real-World Example

  • Converting a USB-C port to a USB-A port using a physical adapter.
  • Java’s Arrays.asList() method, which adapts an array to a List interface.
  • Wrapping a legacy database API to work with a modern application’s interface.

By using the Adapter pattern, the bank can integrate multiple third-party payment gateways into its existing system without needing to modify its core payment processing logic, thus maintaining compatibility and flexibility in handling various payment services.

Scroll to Top