The Adapter design pattern is a structural design pattern that allows objects with incompatible interfaces to work together. The adapter acts as a bridge between the incompatible interfaces, converting the interface of a class into another interface that the client expects. This pattern is especially useful when you want to use an existing class but its interface does not match the one you need. The key components of this pattern are the Target, the Adapter, the Adaptee, and the Client.
Let's implement a simple example where we have a payment gateway that expects payments in a certain format, but we need to use an existing payment system that provides a different interface.
// Target interface
class PaymentGateway {
makePayment(amount) {
throw new Error("This method should be overridden!");
}
}
// Adaptee class
class OldPaymentSystem {
processPayment(amount) {
console.log(
`Processing payment of ${amount} through the old payment system.`
);
}
}
// Adapter class
class PaymentAdapter extends PaymentGateway {
constructor(oldPaymentSystem) {
super();
this.oldPaymentSystem = oldPaymentSystem;
}
makePayment(amount) {
this.oldPaymentSystem.processPayment(amount);
}
}
// Client code
const oldPaymentSystem = new OldPaymentSystem();
const paymentAdapter = new PaymentAdapter(oldPaymentSystem);
paymentAdapter.makePayment(100);
In this implementation, the OldPaymentSystem class represents an existing payment system with a method processPayment that processes payments. The PaymentGateway class is the target interface that defines the method makePayment.
The PaymentAdapter class extends PaymentGateway and adapts the OldPaymentSystem to the PaymentGateway interface by translating the makePayment call to processPayment. The client code creates an instance of OldPaymentSystem and adapts it using PaymentAdapter, allowing the makePayment method to be called, which internally calls processPayment.
Let's create a more involved implementation of the Adapter design pattern by simulating a media player that can play audio and video files in different formats. We need to adapt an existing library that plays only MP3 files to support other formats like MP4 and AVI.
// Target interface
class MediaPlayer {
play(fileName, fileType) {
throw new Error("This method should be overridden!");
}
}
// Adaptee class
class Mp3Player {
playMp3(fileName) {
console.log(`Playing MP3 file: ${fileName}`);
}
}
// Additional Adaptee class for playing MP4 files
class Mp4Player {
playMp4(fileName) {
console.log(`Playing MP4 file: ${fileName}`);
}
}
// Additional Adaptee class for playing AVI files
class AviPlayer {
playAvi(fileName) {
console.log(`Playing AVI file: ${fileName}`);
}
}
// Adapter class
class MediaAdapter extends MediaPlayer {
constructor(fileType) {
super();
this.fileType = fileType;
if (fileType === "mp3") {
this.mediaPlayer = new Mp3Player();
} else if (fileType === "mp4") {
this.mediaPlayer = new Mp4Player();
} else if (fileType === "avi") {
this.mediaPlayer = new AviPlayer();
}
}
play(fileName, fileType) {
if (fileType === "mp3") {
this.mediaPlayer.playMp3(fileName);
} else if (fileType === "mp4") {
this.mediaPlayer.playMp4(fileName);
} else if (fileType === "avi") {
this.mediaPlayer.playAvi(fileName);
} else {
console.log(`Unsupported file type: ${fileType}`);
}
}
}
// Client code
const audioPlayer = new MediaAdapter("mp3");
audioPlayer.play("song.mp3", "mp3");
const videoPlayerMp4 = new MediaAdapter("mp4");
videoPlayerMp4.play("video.mp4", "mp4");
const videoPlayerAvi = new MediaAdapter("avi");
videoPlayerAvi.play("movie.avi", "avi");