Adapter Patern
The Adapter pattern enables incompatible interfaces to collaborate by converting one interface into another expected by the client. It acts as a bridge between legacy and modern components without altering their internal logic.
JavaScript Example
Suppose we need to integrate a new audio player with an existing system that only supports MP3. We wrap the legacy system with an adapter that normalizes the interface.
class AudioRenderer {
play(format, path) {
throw new Error('Interface not implemented');
}
}
class LegacyCodec {
playMp3(filename) {
console.log(`Playing MP3: ${filename}`);
}
}
class AudioBridge extends AudioRenderer {
constructor() {
super();
this.codec = new LegacyCodec();
}
play(format, path) {
const handlers = {
mp3: () => this.codec.playMp3(path),
wav: () => this.transcodeAndPlay(path, 'WAV'),
ogg: () => this.transcodeAndPlay(path, 'OGG')
};
(handlers[format] || (() => console.log('Unsupported format')))();
}
transcodeAndPlay(file, format) {
console.log(`Converting ${format} to playable stream: ${file}`);
}
}
const player = new AudioBridge();
player.play('mp3', 'track.mp3');
player.play('wav', 'sound.wav');
PHP Example
Integrating an old payment processor with a modern gateway using adapter pattern.
interface PaymentProcessor {
public function charge(float $amount): void;
}
class LegacyGateway {
public function executeTransaction(float $amount): void {
echo "Legacy system processed: \${$amount}\n";
}
}
class PaymentAdapter implements PaymentProcessor {
private $legacy;
public function __construct(LegacyGateway $legacy) {
$this->legacy = $legacy;
}
public function charge(float $amount): void {
$this->legacy->executeTransaction($amount);
}
}
$legacy = new LegacyGateway();
$adapter = new PaymentAdapter($legacy);
$adapter->charge(89.99);
Use Cases
- Integrating third-party libraries with incompatible APIs
- Migrating legacy systems without rewriting dependent code
- Enabling backward compatibility during API evolution
Bridge Pattern
The Bridge pattern decouples an abstraction from its implementation so both can vary independent. This is ideal when multiple dimensions of variation exist.
JavaScript Example
Rendering shapes across different output mediums (screen, printer).
class Renderer {
renderCircle(radius) { throw new Error('Abstract method'); }
}
class ScreenRenderer extends Renderer {
renderCircle(radius) {
console.log(`Rasterizing circle: radius ${radius} on screen`);
}
}
class VectorRenderer extends Renderer {
renderCircle(radius) {
console.log(`Generating SVG circle: radius ${radius}`);
}
}
class Shape {
constructor(renderer) {
this.renderer = renderer;
}
}
class Circle extends Shape {
constructor(renderer, radius) {
super(renderer);
this.radius = radius;
}
draw() {
this.renderer.renderCircle(this.radius);
}
}
const screen = new ScreenRenderer();
const vector = new VectorRenderer();
new Circle(screen, 10).draw();
new Circle(vector, 10).draw();
PHP Example
Separating notification types from delivery channels.
interface DeliveryChannel {
public function transmit(string $message): void;
}
class EmailChannel implements DeliveryChannel {
public function transmit(string $message): void {
echo "Email: {$message}\n";
}
}
class SmsChannel implements DeliveryChannel {
public function transmit(string $message): void {
echo "SMS: {$message}\n";
}
}
abstract class Notification {
protected $channel;
public function __construct(DeliveryChannel $channel) {
$this->channel = $channel;
}
abstract public function send(string $content): void;
}
class UrgentAlert extends Notification {
public function send(string $content): void {
$this->channel->transmit("[URGENT] {$content}");
}
}
$alert = new UrgentAlert(new SmsChannel());
$alert->send("System failure detected");
Use Cases
- Multiple platforms with shared logic (e.g., UI components across OS)
- Combinatorial explosion prevention (e.g., shape × rendering back end)
- Runtime switching of implementations (e.g., database drivers)
Composite Pattern
Composite allows treating individual objects and compositions uniformly, forming tree-like structures for hierarchical data.
JavaScript Example
File system with directories and files.
class Node {
constructor(name) {
this.name = name;
}
addChild(node) { throw new Error('Not implemented'); }
removeChild(node) { throw new Error('Not implemented'); }
traverse(depth = 0) { throw new Error('Not implemented'); }
}
class File extends Node {
traverse(depth) {
console.log(' '.repeat(depth) + '📄 ' + this.name);
}
}
class Folder extends Node {
constructor(name) {
super(name);
this.children = [];
}
addChild(node) {
this.children.push(node);
}
removeChild(node) {
this.children = this.children.filter(n => n !== node);
}
traverse(depth = 0) {
console.log(' '.repeat(depth) + '📁 ' + this.name);
this.children.forEach(child => child.traverse(depth + 1));
}
}
const root = new Folder('home');
const docs = new Folder('documents');
const photo = new File('vacation.jpg');
docs.addChild(new File('report.pdf'));
docs.addChild(photo);
root.addChild(docs);
root.traverse();
PHP Example
interface Component {
public function operation(): void;
public function add(Component $child): void;
public function remove(Component $child): void;
}
class TextFile implements Component {
private $name;
public function __construct($name) {
$this->name = $name;
}
public function operation(): void {
echo "Processing file: {$this->name}\n";
}
public function add(Component $child): void {
throw new Exception("Files cannot contain children");
}
public function remove(Component $child): void {
throw new Exception("Files cannot contain children");
}
}
class Directory implements Component {
private $name;
private $items = [];
public function __construct($name) {
$this->name = $name;
}
public function add(Component $child): void {
$this->items[] = $child;
}
public function remove(Component $child): void {
$this->items = array_filter($this->items, fn($item) => $item !== $child);
}
public function operation(): void {
echo "Entering directory: {$this->name}\n";
foreach ($this->items as $item) {
$item->operation();
}
}
}
$root = new Directory('project');
$root->add(new TextFile('README.md'));
$root->add(new TextFile('config.json'));
$root->operation();
Use Cases
- File systems, UI component trees
- Organization hierarchies (departments → teams → employees)
- XML/JSON parsing where nested elements are uniform
Decorator Pattern
Decorator dynamically adds responsibilities to objects without subclassing. It promotes composition over inheritance.
JavaScript Example
Enhancing text formatting with layered decorators.
class Text {
constructor(content) {
this.content = content;
}
render() {
return this.content;
}
}
class TextDecorator extends Text {
constructor(text) {
super(text.content);
this.wrapped = text;
}
render() {
return this.wrapped.render();
}
}
class BoldDecorator extends TextDecorator {
render() {
return `<b>${super.render()}</b>`;
}
}
class ItalicDecorator extends TextDecorator {
render() {
return `<i>${super.render()}</i>`;
}
}
const base = new Text("Hello");
const boldItalic = new ItalicDecorator(new BoldDecorator(base));
console.log(boldItalic.render()); // <b><i>Hello</i></b>
PHP Example
Building customizable coffee orders with optional add-ons.
abstract class Beverage {
protected $description = 'Unknown';
public function getDescription() { return $this->description; }
abstract public function cost(): float;
}
class DarkRoast extends Beverage {
public function __construct() {
$this->description = 'Dark Roast Coffee';
}
public function cost(): float { return 2.20; }
}
abstract class CondimentDecorator extends Beverage {
public abstract function getDescription();
}
class WhippedCream extends CondimentDecorator {
private $beverage;
public function __construct(Beverage $beverage) {
$this->beverage = $beverage;
}
public function getDescription(): string {
return $this->beverage->getDescription() . ', Whipped Cream';
}
public function cost(): float {
return $this->beverage->cost() + 0.50;
}
}
$coffee = new DarkRoast();
$coffee = new WhippedCream($coffee);
echo $coffee->getDescription() . ": $" . number_format($coffee->cost(), 2);
Use Cases
- Dynamic UI enhancements (borders, shadows, animations)
- Request logging, authentication, or caching layers
- Game item enhancements (weapons with enchantments)
Facade Pattern
Facade provides a simplified interface to a complex subsystem, hiding intricate details behind a single entry point.
JavaScript Example
Unified interface for home automation systems.
class Lighting {
turnOn() { console.log('Lights ON'); }
turnOff() { console.log('Lights OFF'); }
}
class Thermostat {
setTemperature(temp) { console.log(`Temp set to ${temp}°C`); }
}
class Security {
arm() { console.log('Security armed'); }
disarm() { console.log('Security disarmed'); }
}
class HomeAutomation {
constructor() {
this.lights = new Lighting();
this.thermo = new Thermostat();
this.sec = new Security();
}
morningRitual() {
this.lights.turnOn();
this.thermo.setTemperature(22);
this.sec.disarm();
}
nightRitual() {
this.lights.turnOff();
this.thermo.setTemperature(18);
this.sec.arm();
}
}
const system = new HomeAutomation();
system.morningRitual();
PHP Example
Consolidated checkout process in e-commerce.
class InventoryChecker {
public function verify($productId) {
echo "Stock verified for ID: {$productId}\n";
}
}
class OrderProcessor {
public function create($productId, $qty) {
echo "Order created: {$qty} units of {$productId}\n";
}
}
class PaymentGateway {
public function charge($amount) {
echo "Charged: \${$amount}\n";
}
}
class CheckoutFacade {
private $inventory;
private $order;
private $payment;
public function __construct() {
$this->inventory = new InventoryChecker();
$this->order = new OrderProcessor();
$this->payment = new PaymentGateway();
}
public function completePurchase($productId, $qty, $amount) {
$this->inventory->verify($productId);
$this->order->create($productId, $qty);
$this->payment->charge($amount);
}
}
$checkout = new CheckoutFacade();
$checkout->completePurchase('SKU-789', 2, 199.99);
Use Cases
- API gateways for microservices
- Legacy system wrappers
- SDKs that abstract underlying complexity
Flyweight Pattern
Flyweight reduces memory usage by sharing common data among similar objects. Ideal for large-scale object creation with repetitive properties.
JavaScript Example
Sharing font and color properties in a text editor.
class Glyph {
constructor(font, color) {
this.font = font;
this.color = color;
}
render(char, x, y) {
console.log(`Rendering '${char}' at (${x},${y}) in ${this.color} ${this.font}`);
}
}
class GlyphFactory {
static cache = new Map();
static getGlyph(font, color) {
const key = `${font}:${color}`;
if (!GlyphFactory.cache.has(key)) {
GlyphFactory.cache.set(key, new Glyph(font, color));
}
return GlyphFactory.cache.get(key);
}
}
function renderText(text, font, color) {
const glyph = GlyphFactory.getGlyph(font, color);
for (let i = 0; i < text.length; i++) {
glyph.render(text[i], i * 10, 50);
}
}
renderText("Hello", "Arial", "blue");
renderText("World", "Arial", "blue"); // Reuses same glyph
PHP Example
Game with thousands of trees using shared models.
interface TreeFlyweight {
public function render(int $x, int $y): void;
}
class PineTree implements TreeFlyweight {
public function render(int $x, int $y): void {
echo "Rendering pine tree at ($x, $y)\n";
}
}
class TreeFactory {
private $pool = [];
public function getTree(string $type): TreeFlyweight {
if (!isset($this->pool[$type])) {
$this->pool[$type] = match ($type) {
'pine' => new PineTree(),
default => throw new InvalidArgumentException("Unknown tree type")
};
}
return $this->pool[$type];
}
}
$factory = new TreeFactory();
$tree1 = $factory->getTree('pine');
$tree2 = $factory->getTree('pine');
$tree1->render(100, 200);
$tree2->render(110, 210); // Same object reused
Use Cases
- Graphics engines with repeated sprites
- Text editors with recurring character styles
- Database connection pooling
Proxy Pattern
Proxy controls access to an object, enabling lazy initialization, access control, or logging.
JavaScript Example
Lazy-loaded image proxy for performance optimization.
class ImageResource {
constructor(url) {
this.url = url;
this.loaded = false;
}
load() {
console.log(`Loading image: ${this.url}`);
this.loaded = true;
}
display() {
if (!this.loaded) this.load();
return `<img alt="loaded" src="${this.url}"></img>`;
}
}
class ImageProxy {
constructor(url) {
this.url = url;
this.realImage = null;
}
display() {
if (!this.realImage) {
this.realImage = new ImageResource(this.url);
}
return this.realImage.display();
}
}
const proxy = new ImageProxy('https://example.com/large.jpg');
document.body.innerHTML = proxy.display(); // Loads only on first access
PHP Example
Deferred database query execution.
class DataFetcher {
private $data;
public function fetch(): array {
if (!$this->data) {
echo "Querying database...\n";
$this->data = range(1, 100000);
}
return $this->data;
}
}
class DataProxy {
private $fetcher;
private $cachedData;
public function __construct() {
$this->fetcher = new DataFetcher();
}
public function getData(): array {
if ($this->cachedData === null) {
$this->cachedData = $this->fetcher->fetch();
}
return $this->cachedData;
}
}
$proxy = new DataProxy();
echo "Accessing data...\n";
$proxy->getData(); // Triggers fetch
$proxy->getData(); // Uses cached result
Use Cases
- Lazy loading of heavy resources
- Access control (authentication proxies)
- Remote service stubs (e.g., SOAP/REST wrappers)
- Logging and monitoring wrappers
Summary: Structural Patterns Comparison
| Pattern | Primary Goal | Key Benefit |
|---|---|---|
| Adapter | Interface compatibility | Integrate without modification |
| Bridge | Separate abstraction from implementation | Independent evolution |
| Composite | Treat objects and groups uniformly | Recursive structure handling |
| Decorator | Dynamic behavior extension | Flexible composition |
| Facade | Simplify complex subsystems | Reduced coupling |
| Flyweight | Minimize memory via sharing | High-scale efficiency |
| Proxy | Control object access | Deferred execution & security |