Java Adapter Design Pattern Tutorial

Avatar

By squashlabs, Last Updated: November 2, 2023

Java Adapter Design Pattern Tutorial

Introduction to Adapter Design Pattern

The Adapter Design Pattern is a structural design pattern that allows incompatible interfaces to work together. It acts as a bridge between two incompatible interfaces, converting the interface of one class into another interface that clients expect. This pattern is useful when integrating existing classes or systems that have incompatible interfaces.

Purpose of Adapter Design Pattern

The purpose of the Adapter Design Pattern is to enable communication and collaboration between classes or systems with incompatible interfaces. It allows these classes to work together by providing a common interface that both sides can understand. This pattern helps to achieve interoperability and reusability by allowing classes to interact without making significant changes to their existing code.

Structural Elements of Adapter Design Pattern

The Adapter Design Pattern consists of the following structural elements:

1. Target: This is the interface that the client code expects to interact with.
2. Adaptee: This is the existing class or system with an incompatible interface.
3. Adapter: This is the class that implements the Target interface and wraps the Adaptee. It translates the requests from the client into calls to the Adaptee.

A better way to build and deploy Web Apps

  Cloud Dev Environments
  Test/QA enviroments
  Staging

One-click preview environments for each branch of code.

Roles of Adapter Design Pattern Components

The components of the Adapter Design Pattern play the following roles:

1. Target: Defines the interface that the client code interacts with.
2. Adaptee: Represents the existing class or system that needs to be adapted.
3. Adapter: Implements the Target interface and adapts the Adaptee’s interface to match the Target interface.

Operation of Adapter Design Pattern

The Adapter Design Pattern operates by receiving a request from the client code through the Target interface. The Adapter then translates this request into appropriate calls to the Adaptee’s interface. The Adaptee performs the required actions and returns the result back to the Adapter, which then returns it to the client code through the Target interface.

Adapter Design Pattern: Use Case 1

One common use case of the Adapter Design Pattern is when integrating a third-party library or service into an existing system. Suppose we have an application that interacts with a legacy payment gateway that uses a different interface than the modern payment gateway we want to integrate. We can create an adapter that translates the requests and responses between the two interfaces, allowing the application to seamlessly work with both payment gateways.

Here’s an example of how the Adapter Design Pattern can be used in Java:

// Target interface
public interface PaymentGateway {
    void processPayment(double amount);
}

// Adaptee class
public class LegacyPaymentGateway {
    public void makePayment(double amount) {
        // Legacy payment gateway logic
    }
}

// Adapter class
public class PaymentGatewayAdapter implements PaymentGateway {
    private LegacyPaymentGateway legacyPaymentGateway;
    
    public PaymentGatewayAdapter(LegacyPaymentGateway legacyPaymentGateway) {
        this.legacyPaymentGateway = legacyPaymentGateway;
    }
    
    @Override
    public void processPayment(double amount) {
        legacyPaymentGateway.makePayment(amount);
    }
}

// Client code
public class Application {
    public static void main(String[] args) {
        // Using the modern payment gateway
        PaymentGateway modernPaymentGateway = new ModernPaymentGateway();
        modernPaymentGateway.processPayment(100.0);
        
        // Using the legacy payment gateway through the adapter
        LegacyPaymentGateway legacyPaymentGateway = new LegacyPaymentGateway();
        PaymentGatewayAdapter adapter = new PaymentGatewayAdapter(legacyPaymentGateway);
        adapter.processPayment(100.0);
    }
}

In the above example, the PaymentGateway interface represents the common interface expected by the client code. The LegacyPaymentGateway class is the existing class with an incompatible interface. The PaymentGatewayAdapter class implements the PaymentGateway interface and adapts the LegacyPaymentGateway interface to match the PaymentGateway interface. The client code can use either the modern payment gateway directly or the legacy payment gateway through the adapter, without any changes to its code.

Adapter Design Pattern: Use Case 2

Another use case of the Adapter Design Pattern is when working with different data formats or protocols. For example, suppose we have an application that needs to process data from a CSV file and a JSON file. Instead of writing separate code to handle each file format, we can create adapters that translate the file-specific operations into a common interface that the application can work with.

Here’s an example of how the Adapter Design Pattern can be used in Java for handling different file formats:

// Target interface
public interface FileReader {
    void readFile(String filePath);
}

// Adaptee classes
public class CsvFileReader {
    public void readCsvFile(String filePath) {
        // Read and process CSV file logic
    }
}

public class JsonFileReader {
    public void readJsonFile(String filePath) {
        // Read and process JSON file logic
    }
}

// Adapter classes
public class CsvFileReaderAdapter implements FileReader {
    private CsvFileReader csvFileReader;
    
    public CsvFileReaderAdapter(CsvFileReader csvFileReader) {
        this.csvFileReader = csvFileReader;
    }
    
    @Override
    public void readFile(String filePath) {
        csvFileReader.readCsvFile(filePath);
    }
}

public class JsonFileReaderAdapter implements FileReader {
    private JsonFileReader jsonFileReader;
    
    public JsonFileReaderAdapter(JsonFileReader jsonFileReader) {
        this.jsonFileReader = jsonFileReader;
    }
    
    @Override
    public void readFile(String filePath) {
        jsonFileReader.readJsonFile(filePath);
    }
}

// Client code
public class Application {
    public static void main(String[] args) {
        FileReader csvFileReader = new CsvFileReaderAdapter(new CsvFileReader());
        FileReader jsonFileReader = new JsonFileReaderAdapter(new JsonFileReader());
        
        csvFileReader.readFile("data.csv");
        jsonFileReader.readFile("data.json");
    }
}

In the above example, the FileReader interface represents the common interface for reading files. The CsvFileReader and JsonFileReader classes are the existing classes with incompatible interfaces for reading CSV and JSON files, respectively. The CsvFileReaderAdapter and JsonFileReaderAdapter classes implement the FileReader interface and adapt the respective file-specific interfaces to match the FileReader interface. The client code can use the adapters to read CSV and JSON files using the common FileReader interface.

More Articles from the How to Write Java Code: From Basics to Advanced Concepts series: