Adaptor Design Pattern

It is an additional layer like an universal power adaptor.

Its a structural design pattern intended to address the compatibility concerns.

Akin to their names, the pattern works very similar to an universal power adaptor:

  • If we think of our gadgets as our clients, it makes them compatible even against the incompatible power points.
  • Again, from a different angle, it provides a stable interface even if the underlying power point types change.
    • For example, our gadgets get the same interface even if we tour from India, to EU, to USA, to China.

In short, it allows us to wrap or re-package a target component to create a more compatible and stable interface for our clients.

In the following section we will look into how it works. And, more importantly, how does it help us in managing the changes happening on our target component.

 

How does the pattern work?

Lets say we are developing an application for an insurance company. And, we are using a shared PDF service providing many utility features like :

  • Create PDF – Based on templates specific to the document types
  • Password Protection – User specific password protection on the PDFs
  • And many other features.

Lets consider just one feature for simplicity. But, being a basic utility module, we use it from various modules as shown.

With this background in mind, lets say we are doing a major upgrade and moving it from a SOAP based service to a REST based service. Then, the diagram below shows the effect of this change, with and without the use of an adapter design pattern.

When we use the option-1, every client using the service will have to incorporate the API changes. But, with option-2, the all changes can easily be incorporated within the adapter. As long as the clients need the same features, there is no need to change the adapter interface. Thus, the adapter keeps the clients unaffected, even against major changes at the target components.

Adaptors in DI Frameworks

When the clients are directly instantiating the adapter object, we have to modify the same adapter for our changes.

But, in DI(Dependency Injection) frameworks like Spring, we can build our adapters by using the programming to interface approach.

Since we can dynamically inject the required implementation, we can either modify the adapter or replace it with a new one as shown. Thus, the clients can simply be dependent on the base adapter interface, remaining unaffected in both the cases. The diagram below just depicts the same.

Source Code for the Demo

For the ease of understanding, we have divided the demo code into two sets.

The first set shows the adapter and its underlying target services- the existing and the upgraded version.

package spectutz.dp.struct.adapter;

import spectutz.dp.struct.adapter.service.NewPDFService;
import spectutz.dp.struct.adapter.service.PDFService;
import spectutz.dp.struct.adapter.vo.Document;
import spectutz.dp.struct.adapter.vo.PDFDocument;

public class PDFServiceAdapter {
	//Code modification required for the upgrade has been kept commented out
	
	//Existing Service: The service class providing the existing version
	private static PDFService pdfService = new PDFService();	
	
	//New Service: The service class providing the upgraded version
	//private static NewPDFService newPdfService = new NewPDFService();
	
	public PDFDocument buildPDF(Document document) {
		//Existing Service: Feature implementation using existing service
		return pdfService.createPDF(document);
		
		//New Service: Feature implementation using upgraded service
		//return newPdfService.generatePDF(document);
	}
}
package spectutz.dp.struct.adapter.service;

import spectutz.dp.struct.adapter.vo.Document;
import spectutz.dp.struct.adapter.vo.PDFDocument;

public class PDFService {
	public PDFDocument createPDF(Document document) {		
		System.out.println("Create a SOAP request to send the document content.");
		System.out.println("Return the generated PDFDocument containing the pdf content.");		
		return new PDFDocument(document.getName(), "PDF", "something, something...");
	}
	
	//Other utility services........
}
package spectutz.dp.struct.adapter.service;

import spectutz.dp.struct.adapter.vo.Document;
import spectutz.dp.struct.adapter.vo.PDFDocument;

public class NewPDFService {
	public PDFDocument generatePDF(Document document) {
		System.out.println("Make a REST call to send the document content.");
		System.out.println("Return the generated PDFDocument containing the pdf content.");		
		return new PDFDocument(document.getName(), "PDF", "something, something...");
	}
	
	//Other utility services........
}

This second set shows the demo client and the remaining files for our reference.

package spectutz.dp.struct.adapter.clients;

import spectutz.dp.struct.adapter.PDFServiceAdapter;
import spectutz.dp.struct.adapter.service.NewPDFService;
import spectutz.dp.struct.adapter.vo.Document;
import spectutz.dp.struct.adapter.vo.PDFDocument;

public class AdapterDemoClient {
	

	public PDFDocument buildPDFWithAdapter(Document document) {
		//This code does not require any change for an upgrade
		PDFServiceAdapter pdfServiceAdapter =new PDFServiceAdapter();
		return pdfServiceAdapter.buildPDF(document);
	}

	public PDFDocument buildPDFWithoutAdapter(Document document) {		
		//This code needs to be changed for an upgrade 
		NewPDFService pdfService =new NewPDFService();
		return pdfService.generatePDF(document);
	}

	public static void main(String[] args) {
		Document demoDoc = new Document("XYZ-TermInsurance-1256", "XYZ-TermInsurance", "{...}");
		
		AdapterDemoClient adapterDemoClient = new AdapterDemoClient();
		
		PDFDocument pdfOne = adapterDemoClient.buildPDFWithAdapter(demoDoc);
		System.out.println("pdfOne :"+ pdfOne.getName() +"\n");
		
		PDFDocument pdfTwo = adapterDemoClient.buildPDFWithoutAdapter(demoDoc);
		System.out.println("pdfTwo :"+ pdfTwo.getName() +"\n");
	}

}
package spectutz.dp.struct.adapter.vo;

public class Document {
	private String name;
	private String type; //enum
	private String jsonContent; //JSON string
	public Document(String name, String type, String jsonContent) {
		this.name = name;
		this.type = type;
		this.jsonContent = jsonContent;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getType() {
		return type;
	}
	public void setType(String type) {
		this.type = type;
	}
	public String getContent() {
		return jsonContent;
	}
	public void setContent(String content) {
		this.jsonContent = content;
	}
}
package spectutz.dp.struct.adapter.vo;

public class PDFDocument {
	private String name;
	private String type; //enum
	private String content; 
	public PDFDocument(String name, String type, String content) {
		this.name = name;
		this.type = type;
		this.content = content;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getType() {
		return type;
	}
	public void setType(String type) {
		this.type = type;
	}
	public String getContent() {
		return content;
	}
	public void setContent(String content) {
		this.content = content;
	}
}

As we see in the domo code, we can implement our features with or without an adapter. But, the adapter can provide the following key advantages:

  1. APIs which are more suited to our application, instead of the generic the APIs of the target components.
  2. The adapter keeps the clients unaffected, by handling the changes triggered by the target components.

Conclusion

When we use a third party component, we do not have much control over its generic interface. But, using an adapter we can transform this into a more compatible which is more suited to our specific usage.

Besides the compatibility concern, it also provides the much needed control over their changes, as we have seen above.

Thus, an adaptor being an additional layer, we may not go for it for all our third party components. But, considering it is a must, if we are planning for their frequent upgrades or likely replacements. And, more so, if we are planning to use the component at lot many places.

As we have seen above, in DI frameworks like Spring, we can use the programming to interface as an effective approach to build our adapters.