The Adapter Design Pattern
also known as Wrapper Pattern
is one of the Structural Design Patterns
. Sometimes data comes from different input sources in different formats. These Incompatible Interfaces
cannot be connected directly, hence an Adapter
or a Connecter
is required which performs the required transformation to obtain the desired results.
The Adapter Design Pattern mainly converts an existing interface into another as per the required formation and allows reusability.
It wraps the response type, hides the transformation complexity, and presents the result in the desired format.
it separates the business logic of the program from the interface or data conversion hence promoting a Single Responsibility Principle
.
Multiple New types of adapters can be introduced in the application without breaking existing code and following the Open-Closed Principle
.
let's understand a few scenarios where Adapter Design Pattern is required.
A speedo meter is designed to show the current speed in the Metrics System
ie kilometers per Hour
used by most of the countries in the world, now the same speedo meter is required in countries that use the Imperial System
(U.S., Liberia, and Myanmar) to display speed in Miles per Hour
. so an adapter is required to transform the data from the Metrics System
to the Imperial System
.
A Memory Card
comes in different standards and shapes (SD, SDHC, SDXC, SDUC, microSD, microSDHC, microSDXC, and microSDUC), and a single USB Port
cannot cater to all the requirements. hence an adapter such as a Card Reader
is required which enables the 2 different interfaces to work together.
Each country has its own Power PLug
, Charging Port
, and Socket
Standards. an Asian Power Plug
will not work in European Sockets
, hence a Power Plug Adapter
is required which has an Asian Style Socket
and European Style Plug
.
A function gets calls multiple APIs
and gets the response in XML
format, now 1 of the API
teams have converted the response from XML
to JSON
format. hence an adapter or transformer is required to get the result.
A Shop
selling Apples
works as an Adapter between Customers
and Vendors
. The same Apple
can be supplied by multiple Vendors
but for Customers
, it will remain the same.
Inheritance Adapter and Composition Adapter
The Adapter Pattern
requires a default value for the Adapter
, it can be implemented in 2 ways, Inheritance
, and Composition
but both produce the same result.
- Inheritance Adapter - The
Inheritance Adapter
takes the help ofJava Inheritance
and the implementation will extend theAdapter
class. it had direct access to the default method. - Composition Adapter - The
Composition Adapter
takes the help ofJava Composition
and the implementation will create an instance of theAdapter
class. the default method will be called using this instance only.
Let's See the Adapter Design Pattern
using Inheritance
package org.wesome.design.patterns;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class Apple {
private String appleName;
private AppleVendor appleVendor;
}
package org.wesome.design.patterns;
public enum AppleVendor {
VENDOR_A, VENDOR_B, VENDOR_C, VENDOR_DEFAULT
}
package org.wesome.design.patterns;
/* it's a shop, all purchases will be made here */
public interface AppleShop {
Apple getFromVendorA(String appleName);
Apple getFromVendorB(String appleName);
Apple getFromVendorC(String appleName);
Apple getFromVendorDefault(String appleName);
}
package org.wesome.design.patterns;
import static org.wesome.design.patterns.AppleVendor.VENDOR_DEFAULT;
public class DefaultVendor {
public Apple getDefaultVendor(String appleName) {
return new Apple(appleName, VENDOR_DEFAULT);
}
}
package org.wesome.design.patterns;
import static org.wesome.design.patterns.AppleVendor.VENDOR_A;
import static org.wesome.design.patterns.AppleVendor.VENDOR_B;
import static org.wesome.design.patterns.AppleVendor.VENDOR_C;
/* This is the apple adaptor, it has methods for each vendor to convert and process accordingly */
public class AppleShopDefaultVendorImpl extends DefaultVendor implements AppleShop {
/* Inheritance Adapter */
@Override
public Apple getFromVendorDefault(String appleName) {
/* the default value method is accessible via inheritance and can be called directly */
return getDefaultVendor(appleName);
}
@Override
public Apple getFromVendorA(String appleName) {
return findVendor(appleName, VENDOR_A);
}
@Override
public Apple getFromVendorB(String appleName) {
return findVendor(appleName, VENDOR_B);
}
@Override
public Apple getFromVendorC(String appleName) {
return findVendor(appleName, VENDOR_C);
}
/* perform all the vendor-specific transformations */
private Apple findVendor(String appleName, AppleVendor appleVendor) {
return new Apple(appleName, appleVendor);
}
}
package org.wesome.design.patterns;
import static org.wesome.design.patterns.AppleVendor.VENDOR_A;
import static org.wesome.design.patterns.AppleVendor.VENDOR_B;
import static org.wesome.design.patterns.AppleVendor.VENDOR_C;
import static org.wesome.design.patterns.AppleVendor.VENDOR_DEFAULT;
public class Adapter {
public static void main(String[] args) {
String appleName = "Macintosh";
AppleShop appleShop = new AppleShopDefaultVendorImpl();
Apple vendorA = getApple(appleShop, appleName, VENDOR_A);
Apple vendorB = getApple(appleShop, appleName, VENDOR_B);
Apple vendorC = getApple(appleShop, appleName, VENDOR_C);
Apple defaultVendor = getApple(appleShop, appleName, VENDOR_DEFAULT);
System.out.println(vendorC.getAppleName() + " apple from vendor " + vendorA.getAppleVendor());
System.out.println(vendorC.getAppleName() + " apple from vendor " + vendorB.getAppleVendor());
System.out.println(vendorC.getAppleName() + " apple from vendor " + vendorC.getAppleVendor());
System.out.println(defaultVendor.getAppleName() + "apple from default Vendor " + defaultVendor.getAppleVendor());
}
public static Apple getApple(AppleShop appleShop, String appleName, AppleVendor appleVendor) {
switch (appleVendor) {
case VENDOR_A:
return appleShop.getFromVendorA(appleName);
case VENDOR_B:
return appleShop.getFromVendorB(appleName);
case VENDOR_C:
return appleShop.getFromVendorC(appleName);
default:
return appleShop.getFromVendorDefault(appleName);
}
}
}
package org.wesome.design.patterns;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import static org.wesome.design.patterns.Adapter.getApple;
import static org.wesome.design.patterns.AppleVendor.VENDOR_A;
import static org.wesome.design.patterns.AppleVendor.VENDOR_B;
import static org.wesome.design.patterns.AppleVendor.VENDOR_C;
import static org.wesome.design.patterns.AppleVendor.VENDOR_DEFAULT;
public class AdapterTest {
@Test
void testAdapterVendorA() {
String appleName = "Macintosh";
AppleShop appleShop = new AppleShopDefaultVendorImpl();
Apple vendorA = getApple(appleShop, appleName, VENDOR_A);
Assertions.assertAll(() -> {
Assertions.assertNotNull(vendorA);
Assertions.assertEquals(appleName, vendorA.getAppleName());
Assertions.assertEquals(VENDOR_A, vendorA.getAppleVendor());
});
}
@Test
void testAdapterVendorB() {
String appleName = "Fuji";
AppleShop appleShop = new AppleShopDefaultVendorImpl();
Apple vendorB = getApple(appleShop, appleName, VENDOR_B);
Assertions.assertAll(() -> {
Assertions.assertNotNull(vendorB);
Assertions.assertEquals(appleName, vendorB.getAppleName());
Assertions.assertEquals(VENDOR_B, vendorB.getAppleVendor());
});
}
@Test
void testAdapterVendorC() {
String appleName = "Gala";
AppleShop appleShop = new AppleShopDefaultVendorImpl();
Apple vendorC = getApple(appleShop, appleName, VENDOR_C);
Assertions.assertAll(() -> {
Assertions.assertNotNull(vendorC);
Assertions.assertEquals(appleName, vendorC.getAppleName());
Assertions.assertEquals(VENDOR_C, vendorC.getAppleVendor());
});
}
@Test
void testAdapterVendorDefault() {
String appleName = "Jonagold";
AppleShop appleShop = new AppleShopDefaultVendorImpl();
Apple vendorDefault = getApple(appleShop, appleName, VENDOR_DEFAULT);
Assertions.assertAll(() -> {
Assertions.assertNotNull(vendorDefault);
Assertions.assertEquals(appleName, vendorDefault.getAppleName());
Assertions.assertEquals(VENDOR_DEFAULT, vendorDefault.getAppleVendor());
});
}
}
plugins {
id 'java'
id "io.freefair.lombok" version "6.2.0"
}
group = 'org.wesome'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = JavaVersion.VERSION_1_8
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter:5.6.2'
}
test {
useJUnitPlatform()
}
Let's See the Adapter Design Pattern
using Composition
package org.wesome.design.patterns;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class Apple {
private String appleName;
private AppleVendor appleVendor;
}
package org.wesome.design.patterns;
public enum AppleVendor {
VENDOR_A, VENDOR_B, VENDOR_C, VENDOR_DEFAULT
}
package org.wesome.design.patterns;
/* it's a shop, all purchases will be made here */
public interface AppleShop {
Apple getFromVendorA(String appleName);
Apple getFromVendorB(String appleName);
Apple getFromVendorC(String appleName);
Apple getFromVendorDefault(String appleName);
}
package org.wesome.design.patterns;
import static org.wesome.design.patterns.AppleVendor.VENDOR_DEFAULT;
public class DefaultVendor {
public Apple getDefaultVendor(String appleName) {
return new Apple(appleName, VENDOR_DEFAULT);
}
}
package org.wesome.design.patterns;
import static org.wesome.design.patterns.AppleVendor.VENDOR_A;
import static org.wesome.design.patterns.AppleVendor.VENDOR_B;
import static org.wesome.design.patterns.AppleVendor.VENDOR_C;
/* This is the apple adaptor, it has methods for each vendor to convert and process accordingly */
public class AppleShopDefaultVendorImpl implements AppleShop {
/* Composition Adapter */
DefaultVendor defaultVendor = new DefaultVendor();
@Override
public Apple getFromVendorDefault(String appleName) {
/* the default value method will be called using instance */
return defaultVendor.getDefaultVendor(appleName);
}
@Override
public Apple getFromVendorA(String appleName) {
return findVendor(appleName, VENDOR_A);
}
@Override
public Apple getFromVendorB(String appleName) {
return findVendor(appleName, VENDOR_B);
}
@Override
public Apple getFromVendorC(String appleName) {
return findVendor(appleName, VENDOR_C);
}
/* perform all the vendor specific transformation */
private Apple findVendor(String appleName, AppleVendor appleVendor) {
return new Apple(appleName, appleVendor);
}
}
package org.wesome.design.patterns;
import static org.wesome.design.patterns.AppleVendor.VENDOR_A;
import static org.wesome.design.patterns.AppleVendor.VENDOR_B;
import static org.wesome.design.patterns.AppleVendor.VENDOR_C;
import static org.wesome.design.patterns.AppleVendor.VENDOR_DEFAULT;
public class Adapter {
public static void main(String[] args) {
String appleName = "Macintosh";
AppleShop appleShop = new AppleShopDefaultVendorImpl();
Apple vendorA = getApple(appleShop, appleName, VENDOR_A);
Apple vendorB = getApple(appleShop, appleName, VENDOR_B);
Apple vendorC = getApple(appleShop, appleName, VENDOR_C);
Apple defaultVendor = getApple(appleShop, appleName, VENDOR_DEFAULT);
System.out.println(vendorC.getAppleName() + " apple from vendor " + vendorA.getAppleVendor());
System.out.println(vendorC.getAppleName() + " apple from vendor " + vendorB.getAppleVendor());
System.out.println(vendorC.getAppleName() + " apple from vendor " + vendorC.getAppleVendor());
System.out.println(defaultVendor.getAppleName() + "apple from default Vendor " + defaultVendor.getAppleVendor());
}
public static Apple getApple(AppleShop appleShop, String appleName, AppleVendor appleVendor) {
switch (appleVendor) {
case VENDOR_A:
return appleShop.getFromVendorA(appleName);
case VENDOR_B:
return appleShop.getFromVendorB(appleName);
case VENDOR_C:
return appleShop.getFromVendorC(appleName);
default:
return appleShop.getFromVendorDefault(appleName);
}
}
}
package org.wesome.design.patterns;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import static org.wesome.design.patterns.Adapter.getApple;
import static org.wesome.design.patterns.AppleVendor.VENDOR_A;
import static org.wesome.design.patterns.AppleVendor.VENDOR_B;
import static org.wesome.design.patterns.AppleVendor.VENDOR_C;
import static org.wesome.design.patterns.AppleVendor.VENDOR_DEFAULT;
public class AdapterTest {
@Test
void testAdapterVendorA() {
String appleName = "Macintosh";
AppleShop appleShop = new AppleShopDefaultVendorImpl();
Apple vendorA = getApple(appleShop, appleName, VENDOR_A);
Assertions.assertAll(() -> {
Assertions.assertNotNull(vendorA);
Assertions.assertEquals(appleName, vendorA.getAppleName());
Assertions.assertEquals(VENDOR_A, vendorA.getAppleVendor());
});
}
@Test
void testAdapterVendorB() {
String appleName = "Fuji";
AppleShop appleShop = new AppleShopDefaultVendorImpl();
Apple vendorB = getApple(appleShop, appleName, VENDOR_B);
Assertions.assertAll(() -> {
Assertions.assertNotNull(vendorB);
Assertions.assertEquals(appleName, vendorB.getAppleName());
Assertions.assertEquals(VENDOR_B, vendorB.getAppleVendor());
});
}
@Test
void testAdapterVendorC() {
String appleName = "Gala";
AppleShop appleShop = new AppleShopDefaultVendorImpl();
Apple vendorC = getApple(appleShop, appleName, VENDOR_C);
Assertions.assertAll(() -> {
Assertions.assertNotNull(vendorC);
Assertions.assertEquals(appleName, vendorC.getAppleName());
Assertions.assertEquals(VENDOR_C, vendorC.getAppleVendor());
});
}
@Test
void testAdapterVendorDefault() {
String appleName = "Jonagold";
AppleShop appleShop = new AppleShopDefaultVendorImpl();
Apple vendorDefault = getApple(appleShop, appleName, VENDOR_DEFAULT);
Assertions.assertAll(() -> {
Assertions.assertNotNull(vendorDefault);
Assertions.assertEquals(appleName, vendorDefault.getAppleName());
Assertions.assertEquals(VENDOR_DEFAULT, vendorDefault.getAppleVendor());
});
}
}
plugins {
id 'java'
id "io.freefair.lombok" version "6.2.0"
}
group = 'org.wesome'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = JavaVersion.VERSION_1_8
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter:5.6.2'
}
test {
useJUnitPlatform()
}
it's possible to create a two-way adapter
that can convert the data in both directions.