Adapter Pattern
Introduction
The Adapter pattern, also known as the Wrapper pattern, allows for the interface of an existing class to be used as another interface. It’s particularly useful when making existing classes work with others without modifying their source code.
Purpose
- To convert the interface of a class into another interface clients expect.
- To enable classes with incompatible interfaces to work together.
Use Cases
- When there is a need to use an existing class, and its interface does not match the one you need.
- When you want to create a reusable class that cooperates with classes which don’t necessarily have compatible interfaces.
- In legacy code integration, where the code cannot be modified.
Advantages/Disadvantages
Advantages:
- Increases compatibility between classes with different interfaces.
- Helps to achieve the Open/Closed Principle by allowing new adapter classes without altering existing code.
Disadvantages:
- Increases overall complexity by introducing a set of new abstractions.
- Sometimes overuse can lead to a system that’s hard to understand due to many layers of indirection.
Implementation in JavaScript
class Target {
request() {
return 'Target: Default behavior';
}
}
class Adaptee {
specificRequest() {
return 'Adaptee: Specific behavior';
}
}
class Adapter extends Target {
constructor(adaptee) {
super();
this.adaptee = adaptee;
}
request() {
return `Adapter: (Translated) ${this.adaptee.specificRequest()}`;
}
}
// Usage
const adaptee = new Adaptee();
const adapter = new Adapter(adaptee);
console.log(adapter.request()); // "Adapter: (Translated) Adaptee: Specific behavior"
Practical Example
Imagine integrating a new logging system into an existing application. The new logger has a different interface from what the application expects. We can use the Adapter pattern to bridge this gap.
class LegacyLogger {
logMessage(msg) {
console.log(`Legacy Logger: ${msg}`);
}
}
class NewLogger {
output(msg) {
console.log(`New Logger: ${msg}`);
}
}
class LoggerAdapter {
constructor(newLogger) {
this.newLogger = newLogger;
}
logMessage(msg) {
// Adapting the interface of NewLogger to LegacyLogger
this.newLogger.output(msg);
}
}
// Usage
const oldLogger = new LegacyLogger();
const newLogger = new NewLogger();
const adaptedLogger = new LoggerAdapter(newLogger);
oldLogger.logMessage('Message for the old logger'); // Legacy Logger: Message for the old logger
adaptedLogger.logMessage('Message for the new logger'); // New Logger: Message for the new logger
In this practical example, LoggerAdapter
adapts NewLogger
’s interface to the interface expected by the existing application (LegacyLogger
). This allows the application to use the new logging system without changing its existing code.