DEV Community

Gaurav
Gaurav

Posted on • Updated on

Decode Adapter Pattern

When to use

  • To wrap an existing class with a new interface.
  • To perform impedance matching

Intent

Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.

Components

  • Target - defines the domain-specific interface that Client uses.
  • Adapter - adapts the interface Adaptee to the Target interface.
  • Adaptee - defines an existing interface that needs adapting.
  • Client - collaborates with objects conforming to the Target interface.

Structure

  • Before
    Adaptor before

  • After
    Adaptor after

Implementation

Assume that you have an e-commerce application which is serving your customers for a long time. This e-commerce application is using a Legacy Order Management System (OMS). Due to the high maintenance cost and degraded performance of the legacy OMS software, you have decided to use a cheap and efficient OMS software which is readily available in the market. However, you realize that the interfaces are different in the new software and it requires a lot of code change in the existing e-commerce application.

Adapter design pattern can be very useful in these situations. Instead of modifying your e-commerce application to use the new interfaces, you can write a 'wrapper' class that acts as a bridge between your e-commerce application and the new OMS software. With this approach, the e-commerce application can still use the old interface.

Adapter design pattern can be implemented in two ways. One using the inheritance method (Class Adapter) and second using the composition (Object Adapter). The following example depicts the implementation of Object adapter.

1 Below is the code that uses the LegacyOMS.

package com.gaurav.adapter;

public class Item {
  private String name;
  private double price;

  public Item(String name, double price) {
    this.name = name;
    this.price = price;
  }

  public String getName() {
    return name;
  }

  public double getPrice() {
    return price;
  }
}
Enter fullscreen mode Exit fullscreen mode
package com.gaurav.adapter;

public class Payment {

  public String type;
  public double amount;

  public Payment(String type, double amount) {
    super();
    this.type = type;
    this.amount = amount;
  }

  public void pay() {
    System.out.println(type + " " + amount + "$");
  }

}
Enter fullscreen mode Exit fullscreen mode
package com.gaurav.adapter;

import java.util.ArrayList;
import java.util.List;

public class LegacyOMS {

  /* The Legacy OMS accepts input in XML format */

  List cart = new ArrayList();
  List payments = new ArrayList();

  public void addItem(Item itemXml) {
    cart.add(itemXml);
    System.out.println(itemXml.getName() + " " + itemXml.getPrice());
  }

  public void makePayment(Payment paymentXml) {
    payments.add(paymentXml);
    paymentXml.pay();
  }
}
Enter fullscreen mode Exit fullscreen mode

2 The client code.

package com.gaurav.client;

import com.gaurav.adapter.Item;
import com.gaurav.adapter.OMSAdapter;
import com.gaurav.adapter.Payment;

public class AdapterClient {

  public static void main(String[] args) {

    /* Create an order and add items */

    LegacyOMS oms = new LegacyOMS();

    oms.addItem(new Item("Italian Pizza", 6.99));
    oms.addItem(new Item("Wine", 9.99));
    oms.addItem(new Item("Beer", 5.99));
    oms.addItem(new Item("Red Apple", 1.49));
    oms.addItem(new Item("Almonds", 11.99));

    System.out.println("---------------------------------");
    /* Create payment and make payment */
    oms.makePayment(new Payment("CASH", 20.00));
    oms.makePayment(new Payment("CREDIT", 10.00));
    oms.makePayment(new Payment("DEBIT", 10.00));
    System.out.println("---------------------------------");

  }
}
Enter fullscreen mode Exit fullscreen mode

3 When the OMS needs to be swapped, you can simply create an Adapter class with same interface that the client uses. This adapter/wrapper class "maps" the client interface to the adaptee (New OMS) interface.

package com.gaurav.adapter;

import java.util.ArrayList;
import java.util.List;

public class NewOMS {

  /* The new OMS accepts input in JSON format */

  List cart = new ArrayList();
  List payments = new ArrayList();

  public void addToBasket(Item itemJson) {
    cart.add(itemJson);
    System.out.println(itemJson.getName() + " " + itemJson.getPrice());
  }

  public void pay(Payment paymentJson) {
    payments.add(paymentJson);
    paymentJson.pay();
  }
}
Enter fullscreen mode Exit fullscreen mode
package com.gaurav.adapter;

public class OMSAdapter {

  /* Object Adapter uses composition */
  private NewOMS newOMS;

  public OMSAdapter() {
    newOMS = new NewOMS();
  }

  public void addItem(Item item) {
    convertXmlToJson(item);
    newOMS.addToBasket(item);
  }

  public void makePayment(Payment p) {
    convertXmlToJson(p);
    newOMS.pay(p);
  }

  /* The new OMS accepts only Json input.
   * Convert the client requests from XML to Json*/
  private void convertXmlToJson(Object o) {
    System.out.println("Converted from XML to JSON");
  }
}
Enter fullscreen mode Exit fullscreen mode

4 The new client code. The client interacts in the same way as before.

package com.gaurav.client;

import com.gaurav.adapter.Item;
import com.gaurav.adapter.OMSAdapter;
import com.gaurav.adapter.Payment;

public class AdapterClient {

  public static void main(String[] args) {

    /* Create an order and add items */

    //LegacyOMS oms = new LegacyOMS();
    /* Use Adapter class with the same interface */
    OMSAdapter oms = new OMSAdapter();

    oms.addItem(new Item("Italian Pizza", 6.99));
    oms.addItem(new Item("Wine", 9.99));
    oms.addItem(new Item("Beer", 5.99));
    oms.addItem(new Item("Red Apple", 1.49));
    oms.addItem(new Item("Almonds", 11.99));

    System.out.println("---------------------------------");
    /* Create payment and make payment */
    oms.makePayment(new Payment("CASH", 20.00));
    oms.makePayment(new Payment("CREDIT", 10.00));
    oms.makePayment(new Payment("DEBIT", 10.00));
    System.out.println("---------------------------------");

  }
}
Enter fullscreen mode Exit fullscreen mode

Output

[output]
Italian Pizza   6.99
Wine            9.99
Beer            5.99
Red Apple       1.49
Almonds        11.99
---------------------------------
CASH    20.0$
CREDIT  10.0$
DEBIT   10.0$
---------------------------------

Enter fullscreen mode Exit fullscreen mode

Benefits

  • Class adapter can override adaptee's behavior.
  • Objects adapter allows a single adapter to work with many adaptees.
  • Helps achieve reusability and flexibility.
  • Client class is not complicated by having to use a different interface and can use polymorphism to swap between different implementations of adapters.

Drawbacks

  • Object adapter involves an extra level of indirection.

Real World Examples

  • Power adapters
  • Memory card adapters

Software Examples

  • Wrappers used to adopt 3rd parties libraries and frameworks.

Java SDK Examples

java.util.Arrays asList()
java.util.Collections list()
java.util.Collections enumeration()
java.io.InputStreamReader(InputStream) (returns a Reader)
java.io.OutputStreamWriter(OutputStream) (returns a Writer)

Want to discuss more
Lets have a Coffee

Discussion (0)