The adapter pattern

The name adapter is derived from the real world, wherein we have various adapters being used in electrical and mechanical domains. While building an application, we often come across scenarios where we have to integrate with other applications/code, which are incompatible. Often, this results in lots of changes being made to either the base code or third-party code. But, in real-world scenarios, there can be various reasons wherein changes cannot be made to either the base code or third-party code or even both.

Note

The adapter design pattern enables two incompatible classes to interact with each other, without those classes knowing about each other's implementation.

Universal Call Center is expanding and wants to enable its customers to pay via credit cards for their services. Assume that there is a third-party library available named PaymentForYou, which helps processing payments using PayPal. So, developers created a class that will send the payment processing request to a third-party library of PaymentForYou, which expects the request in XML format:

public class InvoicePaymentService{
 /**
 * This method invokes third party library to submit payment processing
 request
 */
 public void submitPaymentRequest(Invoice__c invoice){
 String xmlPaymentRequest;
 //... start
 //... some code to generate XML for payment processing
 //... finish
 //PaymentForYouProcessor is class name from third party 
 // service with static method submitRequest
 PaymentForYouProcessor paymentProcessor = new PaymentForYouProcessor();
 paymentProcessor.submitRequest(xmlPaymentRequest);
 }
}

Further, similar code is implemented for various other requirements where payment processing is required, such as invoice payment, quote payment, recurring billing, and so on.

The adapter pattern

After six months, developers at Universal Call Center wanted to move to a different third-party payment processing service. As you can imagine, this means lots of changes to be made to the code wherever PaymentForYouProcessor is used in the existing code base. Further, all the changes will need rigorous testing and quality assurance checks.

So, the team decides to restructure the code to ensure that their functionalities are not tightly coupled with the payment service, and any changes made to the payment service can be managed/executed with ease and lesser impact.

The adapter pattern resolves the current problem by introducing the concept of the adapter class, which is responsible for working with two incompatible classes, to help them work together.

The following diagram shows how using a new PaymentAdapter adapter class, we can establish loose coupling between the PaymentForYouProcessor service and its implementation code:

The adapter pattern

/** 
 *    It interacts with external Payment service and loosely coupled 
 *  with existing code 
 * */  
public class PaymentAdapter{ 
  PaymentForYouProcessor processor; 
     
  //submit request to processor class to invoke payment processing 
  public void submitRequest(String xmlData){ 
    processor.submitRequest(xmlData); 
  } 
}  
 
public class InvoicePaymentService{ 
  /** 
   * This method invokes third party library to submit payment processing request 
   */ 
  public void submitPaymentRequest(Invoice__c invoice){ 
    String xmlPaymentRequest; 
         
    /** 
     * code to generate XML for payment processing 
     */ 
    PaymentAdapter adapter = new PaymentAdapter(); 
    adapter.submitRequest(xmlPaymentRequest); 
  } 
} 
 
public class QuotePaymentService{ 
    /** 
     * This method invokes third party library to submit payment processing request 
     */ 
  public void submitPaymentRequest(Quote invoice){ 
    String xmlPaymentRequest; 
         
    /** 
     * code to generate XML for payment processing 
     */ 
    PaymentAdapter adapter = new PaymentAdapter(); 
    adapter.submitRequest(xmlPaymentRequest); 
  } 
} 

As mentioned earlier, the PaymentAdapter adapter class creates an object of PaymentForYouProcessor and encapsulates the logic to invoke it. Hereby, all the underlying functionalities are not dependent on the PaymentForYouProcessor class directly. Any change in a third-party will mean a change in the adapter class only and not any other underlying classes. This is also known as the object adapter pattern.

Note

The object adapter pattern encapsulates the third-party implementation and exposes the functionality in such a way that it is compatible with the application code. Hence, the application code is able to use the third-party code indirectly, without knowing the underlying implementation. It is important to note that the adapter class was identified at compile time while instantiating its class.

The object adapter pattern is very useful in situations where a service (generally, an external service) is to be used in multiple functionalities, and developers need to achieve loose coupling between a service and its implementations.

Some common scenarios are as follows:

  • Integration with an external web service
  • Using APIs provided by AppExchange applications

Now, with more and more customers using the online payment feature, users demand support for various other cards, such as MasterCard, American Express, and so on. Now, this poses another challenge for developers. With this sudden deluge of new payment processing types, developers want to achieve loose coupling between the application code and payment processing libraries. This approach will help teams to switch payment services at any time while having the least impact on the existing functionality. Assume that there is a third-party library named PayByCard that handles the payment using all major credit and debit cards available in the market.

However, PayByCard uses the JSON data format for data interchange as opposed to using XML. So, it becomes an additional complexity for all the application code to understand the data format that is expected by each payment library, that is, PayByCard (JSON) and PaymentForYou (XML).

Tip

The adapter pattern is also referred to as glue code as this code is generally used to patch up two distinct code sets.

Here, we can create an adapter implementation for each payment library. Further, each adapter will contain the logic to generate data in the required data format (XML or JSON) based on the underlying third-party payment library. This will help all the existing system functionalities to use any payment library uniformly and without changing any existing code.

We have already explored the factory method pattern in the previous chapter and know its advantage of being able to decouple code to instantiate objects. We can use this pattern to handle the instantiation of the correct adapter class to handle the payment processing.

The following table shows the various components used in our example:

Class category

Usage

Processor

These represent classes of a third-party library that cannot be changed. (PaymentForYouProcessor and PayByCardProcessor)

Adapter

These contain logic to interact with a processor class of a third-party library to provide uniform access (PaymentForYouAdapter and PayByCardAdapter)

Adapter contract

This contract needs to be implemented by all PaymentAdapter classes (IPaymentAdapter)

Wrapper

This works as a data carrier (PaymetRequest)

Factory

This helps instantiate an adapter at runtime (PaymentAdapterFactory)

Service

These are application code classes

The following class diagram shows the refactored code:

The adapter pattern

 /** 
  * It interacts with external Payment service "PayByCard" and existing  
  * code uses it via adapter resulting in loose coupling  
  **/ 
public class PayByCardProcessor{ 
  public void processPayment(String jsonData){ 
    //process payment 
  }  
} 
 
/** 
* It interacts with external Payment service "PaymentForYou" and  
* existing code uses it via adapter resulting in loose coupling  
**/ 
public class PaymentForYouProcessor{ 
  public void submitRequest(String xmlData){ 
    //Make API call to process payment 
  } 
} 
 
/** 
 * Data container class to be used to send data  
 */ 
public class PaymentRequest{ 
  public Integer Amount {get; set;} 
  public String PaymentMethod {get; set;} 
  public String AuthorizationCode {get; set;} 
} 
 
/** 
* Factory for payment processor adapters 
*/ 
public interface IPaymentAdapter{ 
  void submitRequest(PaymentRequest request); 
} 
 
/** 
 * Adapter class for "PayByCard" payment service 
 **/ 
                   
public class PayByCardAdapter implements IPaymentAdapter{ 
  PayByCardProcessor processor; 
     
  //submit request to processor class to invoke payment processing 
  public void submitRequest(PaymentRequest request){ 
    String jSONData = generateJSONData(request); 
    processor.processPayment(jSONData); 
  } 
 
  /* 
   * placeholder method to generate JSON stream for payment  
   * request as per underlying service requirements 
   */ 
  String generateJSONData(PaymentRequest request){ 
    return 'JSON Data'; 
  } 
} 
 
/** 
 * Adapter implementation for "PaymentForYou" payment service 
 * Hides entire complexity to communicate to underlying service 
 * */  
public class PaymentForYouAdapter implements IPaymentAdapter{ 
  PaymentForYouProcessor processor; 
     
  //submit request to processor class to invoke payment processing 
  public void submitRequest(PaymentRequest request){ 
    String xmlData = generateXMLData(request); 
    processor.submitRequest(xmlData); 
  } 
 
  /* 
   * placeholder method to generate XML stream for payment  
   * request as per underlying service requirements 
   */ 
  String generateXMLData(PaymentRequest request){ 
    return 'XML Data'; 
  } 
}  
 
/** 
 * Factory class to generate Payment adapters 
 **/ 
public class PaymentAdapterFactory{ 
 
  public static IPaymentAdapter getAdapter(String adapterName){ 
    IPaymentAdapter adapter; 
    if(adapterName == 'PaymentForYou'){ 
      adapter = new PaymentForYouAdapter(); 
    } 
    else if(adapterName == 'PayByCard'){ 
      adapter = new PayByCardAdapter(); 
    } 
         
    return adapter; 
  } 
}   
 
/** 
 * Implementation of quote payment functionality 
 * */  
public class QuotePaymentService{ 
  /** 
   * This method invokes third party library to submit  
   * payment processing request 
   */ 
  public void submitPaymentRequest(Quote invoice){ 
    PaymentRequest quotePaymentRequest; 
         
    //send payment processing request 
    IPaymentAdapter adapter = PaymentAdapterFactory.getAdapter('PayNow'); 
    adapter.submitRequest(quotePaymentRequest); 
  } 
} 
 
/** 
 * Implementation of invoice payment functionality 
 * */  
public class InvoicePaymentService{ 
  /** 
   * This method invokes third party library to submit  
   * payment processing request 
   */ 
  public void submitPaymentRequest(Invoice__c invoice){ 
    PaymentRequest invoicePaymentRequest; 
         
    //send payment processing request 
    IPaymentAdapter adapter = PaymentAdapterFactory.getAdapter('PaymentForYou'); 
    adapter.submitRequest(invoicePaymentRequest); 
  } 
}  

As shown in the preceding code, all the application service classes (QuotePaymentService and InvoicePaymentService) use adapter's submitRequest  method. It further invokes the payment processing mechanism for all payment processors uniformly, irrespective of the method name for each third-party payment processors (PaymentForyourProcessor and PayByCardProcessor).

Additionally, the underlying complexity to send data in the expected data format (JSON/XML), as required by each payment processor class, is also hidden from the application classes.

The preceding approach is the runtime implementation of adapters, wherein the application class doesn't know which adapter to use at compile time, also known as the runtime adapter pattern. This is an excellent example of blending two design patterns (factory method and adapter patterns) and creating an innovative solution.

Note

The runtime adapter pattern helps incompatible classes to interact with each other dynamically.

Points to consider

  • The adapter pattern helps solve incompatibility issues between the existing code.
  • Both the façade and adapter design patterns use the wrapper class. However, the adapter pattern is used to allow interactions between the existing systems, but the façade pattern is used to create a new simplified interface.
  • The object adapter pattern is recommended to be used where there is only one type of third-party library involved.
  • The runtime adapter pattern is recommended to be used where you need to support multiple third-party libraries.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset