Adapter Design Pattern in Java

Adapter Design Pattern in Java | The Adapter design pattern is one of the structural design patterns and it is used so that two unrelated interfaces can work together. The object that joins these unrelated interfaces is called an Adapter.

Need of Adapter Design Pattern?

Let us understand it through an example. The projects/services that are external to all the projects and whose services can be used in multiple projects are called external services or external projects. Example:- PayPal, GooglePay, Weather Report Components, BSE/NSE Share components, ICC score component, and e.t.c. PayPal, GooglePay are used by multiple merchant websites for payment services, similarly, Weather Report components are used by various websites to show weather report data, ICC score components are used by multiple sports websites like HotStar, Crickbuzz, Dream11, and others. 

All these are called distributed services or external services or external projects because these services will be used by multiple local and remote applications. Generally, these external components are developed in Web services.

Assume GooglePay has the following method for payment:-

doPayment(String upiId, String bankCode, long accno, float amount) {
}

This method requires “bankCode”. But the client always enters “bank name” in the merchant websites, so merchant websites need an adapter to convert “bank name” to “bankCode”. 

A real-life example of an Adapter:- In our home, electricity run on different volt (220 in Australia/Europe/UK, 120V in the US, 240V in India, and e.t.c) and our electronics like Mobile Phone needs 5V for charging. To make them compatible we need an Adapter or Charger which will also convert AC to DC.

Following are the limitations of keeping logics directly in Local service class to interact with external service:-

  • Logic will not be reusable across the multiple local service class.
  • If external service component details are changed then we need to distrub multiple local service classes.
  • Converting inputs as required for external component and converting outputs as required for external should be done by local service.

To overcome these problems we can use an Adapter design pattern. Adapter converts inputs or outputs as required for source or destination, and also performs necessary communications.

Note:- While making our local projects talking with external projects there is the possibility of having incompatibility issues. In order to solve that incompatibility, we need to develop one Adapter class (Adapter design pattern). It is in no way related to the Adapter class of Java

Adapter DP/Class makes two unrelated interface/comps talking to each other.  Adapter design pattern is all about acting as bride/mediator having logic for converting inputs and outputs. In the process of interacting with external component. 

Adapter class contains the following logics:-

  1. Converting client project supplied inputs as required for external project inputs.
  2. Getting external component reference either directly or using ServiceLocator.
  3. Calling business methods on external component reference and getting the results
  4. Converting external component supplied results as required for the local project (if necessary). For example:- Weather Report Components returns result in Fahrenhite degree but the local project displaying values in Celcuis degree, so it need to be converted. Different countries may have different mesuerment.

Generally external component are in WebServices, but for simplicity we will use normal Java code to build external component.

Adapter Design Pattern Example

For better understanding we will use normal Java code to demonstrate adapter design pattern. But you can use WebServices to make external component. 

Since we are developing external service component class as ordinary Java class that to we found no state in that class so we have taken that external component as singleton Java class.

If that class/logics are developed as real EJB component or WebService component then we don’t need to worry about single object creation (making class as singleton) because the underlying EJB container or WebService framework implementation will take care of that process.

Note:- Always make external component or distributed component is having one interface and one implementation class so that we can pass or supply interface to Client projects or local projects to make them to recieve and hold external component references.

We have PayPal component as external service, it takes the bank code, payment gateway card code to perform online payment. But customer/cleitn gives bank name, and payment gateway name like VISA, MASTER and e.t.c to E-Commerce website like Amazon. In this case we can write an adapter class which takes the bankName, payment gateway name and maps them to the bank code and payment gateway or card code. 

Let us develop PayPal component as external service using Singleton design pattern,

PayPalComp.java

package com.paypal.external;

public interface PayPalComp {
   public String approveAmount(long cardNo, int cardCode, 
                               int bankCode, double amt);
}

PayPalCompImpl.java

package com.paypal.external;

public class PayPalCompImpl implements PayPalComp {
   private static PayPalComp INSTANCE;

   private PayPalCompImpl() {
   }

   // static factory method
   public static PayPalComp getInstance() {
      if (INSTANCE == null) {
         INSTANCE = new PayPalCompImpl();
      }
      return INSTANCE;
   }

   @Override
   public String approveAmount(long cardNo, int cardCode, 
                               int bankCode, double amt) {
      // database interactions and communication operations required
      return cardNo + " has been approved to pay " 
            + amt + " from " + bankCode;
   }
}

The external class contains the logic for Online payment expecting card code or payment gateway code and bank code. Now clients instead of giving bank codes and card codes, give bank names and card name or payment gateway name for Online payment.

In this case, the interface the client expected is different from the actual implementation which has been designed. To fix this problem we need to write one adapter class as shown below:-

PayAmountAdapter.java

package com.amazon.adapter;

import com.paypal.external.PayPalComp;
import com.paypal.external.PayPalCompImpl;

public class PayAmountAdapter {
   public String payShoppingAmount(long cardNo, String cardName, 
                              String bankName, double amt) {
      int cardCode = 0;
      int bankCode = 0;
      PayPalComp payPalComp = null;
      String paymentMsg = null;

      // get card codes from database
      if (cardName.equalsIgnoreCase("Visa"))
         cardCode = 111;
      else if (cardName.equalsIgnoreCase("Master"))
         cardCode = 222;
      else
         throw new IllegalArgumentException("Invalid card name");

      // get bank code from database
      if (bankName.equalsIgnoreCase("Bank of America"))
         bankCode = 1001;
      else if (bankName.equalsIgnoreCase("JPMorgan"))
         bankCode = 1002;
      else
         throw new IllegalArgumentException("Invalid bank name");

      // use PayPal component
      payPalComp = PayPalCompImpl.getInstance();
      paymentMsg = payPalComp.approveAmount(cardNo, 
                                     cardCode, bankCode, amt);

      return paymentMsg;
   }
}

Service class and its implementation,

PayAmountService.java

package com.amazon.service;

public interface PayAmountService {
   public String payShoppingAmount(long cardNo, String cardName, 
                                    String bankName, double amt);
}

PayAmountServiceImpl.java

package com.amazon.service;

import com.amazon.adapter.PayAmountAdapter;

public class PayAmountServiceImpl implements PayAmountService {

   @Override
   public String payShoppingAmount(long cardNo, String cardName, 
                                String bankName, double amt) {
      // use adapter class for payment service
      PayAmountAdapter adapter = new PayAmountAdapter();
      return adapter.payShoppingAmount(cardNo, cardName, 
                                       bankName, amt);
   }
}

Client Application,

package com.amazon.test;

import com.amazon.service.PayAmountService;
import com.amazon.service.PayAmountServiceImpl;

public class ClientApp {
   public static void main(String[] args) {
      PayAmountService shopping = new PayAmountServiceImpl();
      String cfrmMsg = shopping.payShoppingAmount(55443322, 
                       "VISA", "Bank of America", 3000);
      System.out.println(cfrmMsg);
      System.out.println();
      
      cfrmMsg = shopping.payShoppingAmount(123456789, 
                             "Master", "JPMorgan", 5000);
      System.out.println(cfrmMsg);
   }
}

Output:-

55443322 has been approved to pay 3000.0 from 1001

123456789 has been approved to pay 5000.0 from 1002

See Adapter Design Pattern example code at GitHub:- AdapterDP

Predefined JDK methods representing Adapter Design Pattern

  • 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)
  • javax.xml.bind.annotation.adapters.XmlAdapter#marshal() and #unmarshal()
  • Java8 stream() method of streaming API
BufferedReader reader = 
    new BufferedReader(new InputStreamReader(System.in));

Here System.in is Byte stream and BufferedReader is Character stream. Therefore InputStreamReader works as adapter to convert byte stream to character stream. 

If you enjoyed this post, share it with your friends. Do you want to share more information about the topic discussed above or do you find anything incorrect? Let us know in the comments. Thank you!

Leave a Comment

Your email address will not be published.