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.

Components

SOLID Principle

✅ 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)

OOP Langs Implementation

Inheritance (Less Common)

Wrapping behaviour is not recommended via inheritance, even though it doesn’t require wrapping multiple adapters or wrapping at runtime, but introduces unnecessary coupling.

Polymorphism Decision

Interface Polymorphism + Composition

// 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

Go Implementation

Interface Polymorphism + Explicit Composition

To hide the legacy API from the client, it use Explicit Embedding.

Struct Embedding