Posts

Bridge

June 20, 2025
Bridge Pattern
"Decouple an abstraction from its implementation so that the two can vary independently."Gang of Four (GoF)

If you try to extend a class across multiple dimensions (e.g., shape × color, or device × remote), subclassing leads to a combinatorial explosion:
Plaintext
Circle × Red
Circle × Blue
Square × Red
Square × Blue
... and so on.
Adding a new color or shape requires updating many classes. That’s hard to maintain.
Split the hierarchy into two independent class trees:
  • One for abstractions (e.g., shapes, remotes)
  • One for implementations (e.g., colors, devices)
They communicate via a bridge (interface).
Think of a remote control (abstraction) operating different devices (implementation) like TVs or Radios. You don’t redesign the remote every time you buy a new device—it just works if it conforms to the expected interface.
Plaintext
Abstraction
 └── RefinedAbstraction
       └── implementation: Implementation

Implementation (interface)
 ├── ConcreteImplementationA
 └── ConcreteImplementationB

Ts
interface Device {
  isEnabled(): boolean;
  enable(): void;
  disable(): void;
  getVolume(): number;
  setVolume(percent: number): void;
  getChannel(): number;
  setChannel(channel: number): void;
}
Ts
class TV implements Device {
  private on = false;
  private volume = 50;
  private channel = 1;

  isEnabled(): boolean {
    return this.on;
  }

  enable(): void {
    this.on = true;
    console.log("TV is now ON");
  }

  disable(): void {
    this.on = false;
    console.log("TV is now OFF");
  }

  getVolume(): number {
    return this.volume;
  }

  setVolume(percent: number): void {
    this.volume = Math.min(Math.max(percent, 0), 100);
    console.log(`TV volume set to ${this.volume}`);
  }

  getChannel(): number {
    return this.channel;
  }

  setChannel(channel: number): void {
    this.channel = channel;
    console.log(`TV channel set to ${this.channel}`);
  }
}
Ts
class RemoteControl {
  constructor(protected device: Device) {}

  togglePower(): void {
    this.device.isEnabled() ? this.device.disable() : this.device.enable();
  }

  volumeDown(): void {
    this.device.setVolume(this.device.getVolume() - 10);
  }

  volumeUp(): void {
    this.device.setVolume(this.device.getVolume() + 10);
  }

  channelDown(): void {
    this.device.setChannel(this.device.getChannel() - 1);
  }

  channelUp(): void {
    this.device.setChannel(this.device.getChannel() + 1);
  }
}
Ts
class AdvancedRemoteControl extends RemoteControl {
  mute(): void {
    this.device.setVolume(0);
    console.log("Muted");
  }
}
Ts
const tv = new TV();
const remote = new RemoteControl(tv);

remote.togglePower(); // TV is now ON
remote.volumeUp(); // TV volume set to 60
remote.channelUp(); // TV channel set to 2

const smartRemote = new AdvancedRemoteControl(tv);
smartRemote.mute(); // Muted, volume = 0

  • You need to decouple abstraction and implementation.
  • You want to avoid a class explosion when extending in multiple dimensions.
  • You need to change implementations at runtime.
  • You want to develop parts independently (e.g., UI vs backend).

✅ Pros:
  • Open/Closed Principle: add new abstractions/implementations without changes.
  • Improves modularity and flexibility.
  • You can reuse abstractions with many implementations.
❌ Cons:
  • Adds complexity due to additional interfaces and indirection.
  • May be overkill if only one dimension of variation exists.
On this page