Adapter Pattern
Let’s take some real time requirement to understand this pattern
John is a developer working for a client on weather forecasting application and he gets weather details from some third-party system say “X”, he has implemented all the code to consume weather details from “X” system and code is working fine, obviously John is very happy and clients too.
Suddenly one day Client requests John to change the third-party system from “X” to “Y”.
Note : Hope you understand that client will always come with the change whenever they feel.
Now John checks his code, he could find that lot of classes are using “X” system objects in his code and “Y” system objects are not compatible with his code.
If he starts changing all the classes wherever “X” system objects are used, it becomes like he is going to change lot of classes which are already tested, proven and working fine in Production.
What John has to do now to accommodate client’s requirement of using “Y” system without doing much of a change in his code?
This is where Adapter pattern comes to rescue him.
Note : Both the system “X” and “Y” produces same functionality but using different methods.
John knows that objects of “Y” system produces same functionality what “X” system produces.
Let’s understand few details about Adapter pattern before implementing it
Adapter pattern is one of the structural design patterns.
It is mainly used to communicate between 2 incompatible interfaces.
The object that is used to make these incompatible interfaces communicate each other is called as “Adapter”
Adapter lets incompatible interfaces work together that couldn’t otherwise because of incompatibility.
In real world, we can see some electronic adapters like
power supply adapters which will be used when we have incompatible plugs like 2 pins and 3 pins connectors.
Memory card adapter to plug the memory card from one device to another device.
So, adapter always acts as a bridge between incompatible interfaces.
The classes/objects participating in adapter pattern
Target – defines the domain-specific interface that Client uses.
Adaptee – defines an existing interface that needs to be adapted.
Adapter – adapts the interface Adaptee to the Target interface.
Client – collaborates with objects conforming to the Target interface.
Let’s implement the solution to the new modification request from John’s client
Solution to his problem without changing much of his code is Adapter pattern.
As we know adapter pattern acts as a bridge between 2 incompatible interfaces.
Solution can be represented as below
We can see that 2 incompatible systems are connected through Adapter in the above diagram.
Let’s implement the pattern
We can implement the adapter pattern in 2 ways
1) Using Object Adapter – we use composition to achieve this
2) Using Class Adapter – we use inheritance to achieve this.
The best approach to implement this pattern is using Object Adapter and hence will implement the same in this article.
Step 1
Currently our code is using “X” weather system and interface for the same is as below
- package com.kb.adapter;
- public interface XWeather {
- public String getWeatherInfo(String city);
- }
package com.kb.adapter; public interface XWeather { public String getWeatherInfo(String city); }
It contains a method which provides the whether details based on City
Step 2
The following class defines the implementation to the XWeather interface .
- package com.kb.adapter;
- package com.kb.adapter;
- public class XWeatherImpl implements XWeather {
- @Override
- public String getWeatherInfo(String city) {
- if ("Bangalore".equalsIgnoreCase(city)) {
- return "weather info from X system -> 34 degree and light rain possible";
- } else if ("Newyork".equalsIgnoreCase(city)) {
- return "weather info from X system -> 20 degree and heavy rain possible";
- } else {
- return "Sorry weather forecasting not available for given city-> " + city;
- }
- }
- }
package com.kb.adapter; package com.kb.adapter; public class XWeatherImpl implements XWeather { @Override public String getWeatherInfo(String city) { if ("Bangalore".equalsIgnoreCase(city)) { return "weather info from X system -> 34 degree and light rain possible"; } else if ("Newyork".equalsIgnoreCase(city)) { return "weather info from X system -> 20 degree and heavy rain possible"; } else { return "Sorry weather forecasting not available for given city-> " + city; } } }
Step 3
Let’s create 2 classes which uses “XWeather” API
Note : In real time, there could be 100 of such classes which might be using XWeather API.
- package com.kb.adapter;
- public class WeatherUser1 {
- XWeather weather;
- public WeatherUser1(XWeather weather) {
- this.weather=weather;
- }
- public void getWeather(String city){
- String weatherInfo = weather.getWeatherInfo(city);
- System.out.println("User 1 -> "+weatherInfo);
- }
- }
package com.kb.adapter; public class WeatherUser1 { XWeather weather; public WeatherUser1(XWeather weather) { this.weather=weather; } public void getWeather(String city){ String weatherInfo = weather.getWeatherInfo(city); System.out.println("User 1 -> "+weatherInfo); } }
- package com.kb.adapter;
- public class WeatherUser2 {
- XWeather weather;
- public WeatherUser2(XWeather weather) {
- this.weather=weather;
- }
- public void getWeather(String city){
- String weatherInfo = weather.getWeatherInfo(city);
- System.out.println("User 2 -> "+weatherInfo);
- }
- }
package com.kb.adapter; public class WeatherUser2 { XWeather weather; public WeatherUser2(XWeather weather) { this.weather=weather; } public void getWeather(String city){ String weatherInfo = weather.getWeatherInfo(city); System.out.println("User 2 -> "+weatherInfo); } }
Step 4
Create a client test class to use these Weather classes
- package com.kb.adapter;
- public class AdapterClient {
- public static void main(String[] args) {
- //old code using "X" weather system
- System.out.println("Old \"X\" weather system output.....");
- XWeather weatherX = new XWeatherImpl();
- WeatherUser1 weatherUser1 = new WeatherUser1(weatherX);
- weatherUser1.getWeather("Newyork");
- WeatherUser2 weatherUser2 = new WeatherUser2(weatherX);
- weatherUser2.getWeather("Bangalore");
- }
- }
package com.kb.adapter; public class AdapterClient { public static void main(String[] args) { //old code using "X" weather system System.out.println("Old \"X\" weather system output....."); XWeather weatherX = new XWeatherImpl(); WeatherUser1 weatherUser1 = new WeatherUser1(weatherX); weatherUser1.getWeather("Newyork"); WeatherUser2 weatherUser2 = new WeatherUser2(weatherX); weatherUser2.getWeather("Bangalore"); } }
We don’t have any issues till this as our code is using weather system “X”
Now client demands to use “Y” weather system instead of “X” weather system
This is where our adapter comes to rescue us.
Step 5
New weather system “Y” interface looks like below
- package com.kb.adapter;
- public interface YWeather {
- public String getWeatherDetails(String city);
- }
package com.kb.adapter; public interface YWeather { public String getWeatherDetails(String city); }
Step 6
Implementation for the above interface looks as below
- package com.kb.adapter;
- public class YWeatherImpl implements YWeather {
- @Override
- public String getWeatherDetails(String city) {
- if ("Bangalore".equalsIgnoreCase(city)) {
- return "weather info from Y system -> 40 degree and light rain possible";
- } else if ("Newyork".equalsIgnoreCase(city)) {
- return "weather info from Y system -> 25 degree and heavy rain possible";
- } else {
- return "Sorry weather forecasting not available for given city-> " + city;
- }
- }
- }
package com.kb.adapter; public class YWeatherImpl implements YWeather { @Override public String getWeatherDetails(String city) { if ("Bangalore".equalsIgnoreCase(city)) { return "weather info from Y system -> 40 degree and light rain possible"; } else if ("Newyork".equalsIgnoreCase(city)) { return "weather info from Y system -> 25 degree and heavy rain possible"; } else { return "Sorry weather forecasting not available for given city-> " + city; } } }
As you can see, this “Y” system has a different method which need to be used in the code but most of the code is already using “X” weather system API, it’s really hard and risky to change the entire set of classes using it.
We need some way, that’s able to fulfil the client’s new requirement of using “Y” weather system and also, we should make less (or) no change in the current code.
This can be achieved by using Adapter pattern.
We will create an adapter which implements “XWeather”, and it wraps an “YWeather” object (the type it supposes to be adapted).
Step 7
Create the adapter class as below
- package com.kb.adapter;
- public class XToYWeatherAdapter implements XWeather {
- YWeather yWeather;
- public XToYWeatherAdapter(YWeather yWeather) {
- this.yWeather = yWeather;
- }
- @Override
- public String getWeatherInfo(String city) {
- return yWeather.getWeatherDetails(city);
- }
- }
package com.kb.adapter; public class XToYWeatherAdapter implements XWeather { YWeather yWeather; public XToYWeatherAdapter(YWeather yWeather) { this.yWeather = yWeather; } @Override public String getWeatherInfo(String city) { return yWeather.getWeatherDetails(city); } }
In the above code, we have created an adapter which wraps or adapts the “Y” weather system using composition.
Step 8
Let us test the above code and see whether it can solve the Client’s problem.
- package com.kb.adapter;
- public class AdapterClient {
- public static void main(String[] args) {
- //old code using "X" weather system
- System.out.println("Old \"X\" weather system output.....");
- XWeather weatherX = new XWeatherImpl();
- WeatherUser1 weatherUser1 = new WeatherUser1(weatherX);
- weatherUser1.getWeather("Newyork");
- WeatherUser2 weatherUser2 = new WeatherUser2(weatherX);
- weatherUser2.getWeather("Bangalore");
- //Changing old code to use "Y" weather system
- //Only minimal change is required and no need to change in any other class except here
- System.out.println("New \"Y\" weather system output.....");
- YWeather WeatherY = new YWeatherImpl();
- XWeather weatherXToY = new XToYWeatherAdapter(WeatherY);
- WeatherUser1 weatherUser3 = new WeatherUser1(weatherXToY);
- weatherUser3.getWeather("Newyork");
- WeatherUser2 weatherUser4 = new WeatherUser2(weatherXToY);
- weatherUser4.getWeather("Bangalore");
- }
- }
package com.kb.adapter; public class AdapterClient { public static void main(String[] args) { //old code using "X" weather system System.out.println("Old \"X\" weather system output....."); XWeather weatherX = new XWeatherImpl(); WeatherUser1 weatherUser1 = new WeatherUser1(weatherX); weatherUser1.getWeather("Newyork"); WeatherUser2 weatherUser2 = new WeatherUser2(weatherX); weatherUser2.getWeather("Bangalore"); //Changing old code to use "Y" weather system //Only minimal change is required and no need to change in any other class except here System.out.println("New \"Y\" weather system output....."); YWeather WeatherY = new YWeatherImpl(); XWeather weatherXToY = new XToYWeatherAdapter(WeatherY); WeatherUser1 weatherUser3 = new WeatherUser1(weatherXToY); weatherUser3.getWeather("Newyork"); WeatherUser2 weatherUser4 = new WeatherUser2(weatherXToY); weatherUser4.getWeather("Bangalore"); } }
When to use this pattern ?
When there is an existing class which we need to use but its interface does not match as per our need
When we want to create a reusable class that collaborates unrelated or incompatible classes, that is, classes that don’t necessarily have compatible interfaces.
Design patterns are extremely popular among developers. But do you know what design pattern is and what are the types of patterns that are being used by other developers? With this blog, we will help you in understanding the concept of design patterns, different available patterns and scenarios to use them.