Adapter design pattern

What is Adapter design pattern and it’s advantages

What is Adapter design pattern?

Certainly! The Adapter Pattern is a structural design pattern that allows incompatible interfaces to work together. It acts as a bridge between two incompatible interfaces, making them compatible without changing their existing code.

Example# 1:

let’s consider an example where you have a legacy system that uses a socket to connect electronic devices, and you want to introduce a new system that uses an electric base to connect devices. The existing system has a LegacySocket class with methods that are incompatible with the new ElectricBase interface. We’ll create an adapter to make the legacy socket compatible with the new interface.

Adapter design pattern

Here’s the Java code:

Example
/*
 * Author: Zameer Ali Mohil
 * */
// Existing LegacySocket class with an incompatible interface
class LegacySocket {
    public void plugIn(String device) {
        System.out.println("Legacy Socket: Plugged in " + device);
    }
}

// New ElectricBase interface expected by client code
interface ElectricBase {
    void connectDevice(String device);
}

// Adapter class that adapts LegacySocket to ElectricBase interface
class LegacySocketAdapter implements ElectricBase {
    private LegacySocket legacySocket;

    public LegacySocketAdapter(LegacySocket legacySocket) {
        this.legacySocket = legacySocket;
    }

    @Override
    public void connectDevice(String device) {
        // Call the existing LegacySocket method from the adapted interface
        legacySocket.plugIn(device);
    }
}

// Client code that expects ElectricBase interface
class Client {
    public void connectUsingElectricBase(ElectricBase electricBase, String device) {
        electricBase.connectDevice(device);
    }
}

public class AdapterPatternSocketExample {
    public static void main(String[] args) {
        // Creating an instance of LegacySocket
        LegacySocket legacySocket = new LegacySocket();

        // Creating an adapter to make LegacySocket compatible with ElectricBase interface
        ElectricBase electricBase = new LegacySocketAdapter(legacySocket);

        // Client code can now use the ElectricBase interface
        Client client = new Client();
        client.connectUsingElectricBase(electricBase, "Laptop");
    }
}

Let’s consider a basic example where we have an existing LegacyPrinter class with a method print(String) and we want to use it in a new system that expects a Printer interface with methods printDocument(String) and scanDocument(). We’ll create an adapter to make these two interfaces compatible.

In this example, the LegacySocketAdapter class adapts the LegacySocket to the ElectricBase interface. The client code interacts with the ElectricBase interface, and the adapter takes care of translating the method calls to the underlying LegacySocket implementation. This allows you to introduce the new system with the ElectricBase interface without modifying the existing LegacySocket class.


Example# 2:

1. Define the Legacy Interface:

Example
// Legacy interface
interface LegacyPrinter {
    void print(String content);
}

2. Create the Legacy Class:

Example
// Legacy class
class OldPrinter implements LegacyPrinter {
    @Override
    public void print(String content) {
        System.out.println("Printing: " + content);
    }
}

3. Define the Target Interface:

Example
// Target interface
interface Printer {
    void printDocument(String document);
    void scanDocument();
}

4. Create the Adapter Class:

Example
// Adapter class
class PrinterAdapter implements Printer {
    private LegacyPrinter legacyPrinter;

    public PrinterAdapter(LegacyPrinter legacyPrinter) {
        this.legacyPrinter = legacyPrinter;
    }

    @Override
    public void printDocument(String document) {
        // Adapt the call to the existing LegacyPrinter interface
        legacyPrinter.print(document);
    }

    @Override
    public void scanDocument() {
        // Provide a default implementation for scanning
        System.out.println("Scanning not supported by LegacyPrinter");
    }
}

5. Use the Adapter in the Client Code:

Example
// Client code
public class AdapterPatternExample {
    public static void main(String[] args) {
        // Use the LegacyPrinter with the new Printer interface

        // Create an instance of the LegacyPrinter
        LegacyPrinter legacyPrinter = new OldPrinter();

        // Create an adapter to make it compatible with the new Printer interface
        Printer printerAdapter = new PrinterAdapter(legacyPrinter);

        // Use the new Printer interface
        printerAdapter.printDocument("Adapter Pattern Example");
        printerAdapter.scanDocument();
    }
}

In this example:

  • The LegacyPrinter interface represents the existing interface that we want to adapt.
  • The OldPrinter class is the existing class implementing the LegacyPrinter interface.
  • The Printer interface is the target interface that our client code expects.
  • The PrinterAdapter class is the adapter that makes the LegacyPrinter compatible with the Printer interface.
  • In the AdapterPatternExample, we create an instance of OldPrinter (the legacy class) and then use the PrinterAdapter to adapt it to the new Printer interface.

This allows the client code to use the new Printer interface seamlessly with the existing LegacyPrinter implementation, thanks to the adapter pattern.

Advantages of adapter design pattern

The Adapter design pattern in Java, as in other programming languages, offers several advantages:

1. Compatibility and Reusability:

  • Integration with Existing Code: Adapters allow you to integrate new classes or systems with existing code that has incompatible interfaces. This promotes the reuse of legacy code and avoids unnecessary modifications.
  • Interoperability: Adapters enable interoperability between different systems or components with disparate interfaces, fostering compatibility.

2. Minimizes Code Changes: The Adapter pattern helps in minimizing changes to existing code. Instead of modifying the existing code to match new interfaces, you create adapters that mediate between the old and the new.

3. Easy Maintenance: Adapters encapsulate the changes required to make the systems compatible. This encapsulation makes it easier to maintain and modify the adapters without affecting the rest of the codebase.

4. Promotes Separation of Concerns: The Adapter pattern promotes the separation of concerns by isolating the code responsible for adapting the interfaces. This makes the codebase more modular and easier to understand.

5. Facilitates Legacy System Integration: When dealing with legacy systems or third-party libraries, the Adapter pattern is particularly useful. It allows you to integrate new functionality or systems seamlessly without requiring changes to existing, well-established code.

6. Testability: Adapters can simplify the testing process. Since the adapter encapsulates the translation between interfaces, you can easily create test cases for the adapted interfaces without affecting the underlying implementations.

7. Promotes Design Flexibility: The Adapter pattern promotes design flexibility by allowing different implementations to work together. It accommodates changes in requirements without necessitating extensive modifications to the existing codebase.

8. Enhances Code Readability: By using adapters, the code can be more readable and maintainable. Adapters provide a clear separation between the client code and the adapted classes, making it easier to understand the interactions.

9. Facilitates Dependency Inversion: The Adapter pattern adheres to the Dependency Inversion Principle by allowing high-level modules (client code) to depend on abstractions (common interfaces) rather than on specific implementations. This contributes to a more flexible and scalable design.

10. Enables Design Patterns Composition: Adapters can be combined with other design patterns to address complex design scenarios. For example, you might use an Adapter in conjunction with a Composite or Decorator pattern to achieve specific functionality.

In summary, the Adapter design pattern is a valuable tool for achieving interoperability and promoting flexibility in software design. It is particularly useful in scenarios where existing systems or components need to be integrated with new functionality, and their interfaces are initially incompatible.

Leave a Reply

Your email address will not be published. Required fields are marked *