The Adapter pattern allows incompatible interfaces to work together, making it useful when integrating third-party libraries or refactoring legacy code.
But the updates in the legacy code (Adaptee) may also require changes in the Adapter.
✅ Single Responsibility Principle (SRP)
✅ Open/Closed Principle (OCP)
✅ Liskov Substitution Principle (LSP) / Interface Segregation Principle (ISP): ⚠️ If Adapter has an abstraction and complies it.
✅ Dependency Inversion Principle (DIP)
Wrapping behaviour is not recommended via inheritance, even though it doesn’t require wrapping multiple adapters or wrapping at runtime, but introduces unnecessary coupling.
// Adaptee
interface LegacyProduct {
inStorePrice(): number;
}
class LegacyApple implements LegacyProduct {
inStorePrice(): number {
return 100;
}
}
// Adapter
interface Product {
inStorePrice(): number;
onlinePrice(): number;
}
class Apple implements Product {
private legacyProduct: LegacyProduct;
constructor(legacyProduct: LegacyProduct) {
// The product can either be encapsulated or use DI injection.
// - Encapsulation: for fixed type cohesion and simplicity
// - DI: for testability and runtime flexibility
this.legacyProduct = legacyProduct;
}
inStorePrice(): number {
return this.legacyProduct.inStorePrice();
}
onlinePrice(): number {
return this.legacyProduct.inStorePrice() * 0.9;
}
}
// Client
const apple = new Apple(new LegacyApple());
console.log(apple.onlinePrice()); // 90
To hide the legacy API from the client, it use Explicit Embedding.