Skip to content
SP StackPractices
intermediate

Bridge Pattern

Decouple an abstraction from its implementation so both can vary independently. A structural design pattern for platform independence.

Topics: design

Bridge Pattern

Overview

The Bridge Pattern is a structural design pattern that decouples an abstraction from its implementation so that the two can vary independently. Instead of having one class hierarchy that combines both, you split it into two separate hierarchies — one for the abstraction and one for the implementation. This is especially useful when you need to support multiple platforms or rendering backends.

When to Use

Use the Bridge Pattern when:

  • You want to avoid a permanent binding between an abstraction and its implementation
  • Both the abstraction and its implementation should be extensible by subclassing
  • You want to share an implementation among multiple objects
  • Changes in the implementation should not affect clients
  • You have a proliferating class hierarchy from combining dimensions (e.g., shapes × renderers)

Solution

Python

from abc import ABC, abstractmethod

# Implementation hierarchy
class Renderer(ABC):
    @abstractmethod
    def render_circle(self, radius: float):
        pass

class VectorRenderer(Renderer):
    def render_circle(self, radius: float):
        print(f"Drawing a circle of radius {radius} with vector graphics")

class RasterRenderer(Renderer):
    def render_circle(self, radius: float):
        print(f"Drawing pixels for a circle of radius {radius}")

# Abstraction hierarchy
class Shape(ABC):
    def __init__(self, renderer: Renderer):
        self.renderer = renderer

    @abstractmethod
    def draw(self):
        pass

class Circle(Shape):
    def __init__(self, renderer: Renderer, radius: float):
        super().__init__(renderer)
        self.radius = radius

    def draw(self):
        self.renderer.render_circle(self.radius)

# Usage: combine any shape with any renderer
circle_vector = Circle(VectorRenderer(), 5.0)
circle_vector.draw()

circle_raster = Circle(RasterRenderer(), 10.0)
circle_raster.draw()

JavaScript

class VectorRenderer {
  renderCircle(radius) {
    console.log(`Drawing a circle of radius ${radius} with vector graphics`);
  }
}

class RasterRenderer {
  renderCircle(radius) {
    console.log(`Drawing pixels for a circle of radius ${radius}`);
  }
}

class Shape {
  constructor(renderer) {
    this.renderer = renderer;
  }
  draw() {
    throw new Error("Subclasses must implement draw()");
  }
}

class Circle extends Shape {
  constructor(renderer, radius) {
    super(renderer);
    this.radius = radius;
  }

  draw() {
    this.renderer.renderCircle(this.radius);
  }
}

// Usage
const cv = new Circle(new VectorRenderer(), 5);
cv.draw();

const cr = new Circle(new RasterRenderer(), 10);
cr.draw();

Java

public interface Renderer {
    void renderCircle(double radius);
}

public class VectorRenderer implements Renderer {
    public void renderCircle(double radius) {
        System.out.println("Drawing a circle of radius " + radius + " with vector graphics");
    }
}

public class RasterRenderer implements Renderer {
    public void renderCircle(double radius) {
        System.out.println("Drawing pixels for a circle of radius " + radius);
    }
}

public abstract class Shape {
    protected final Renderer renderer;

    public Shape(Renderer renderer) {
        this.renderer = renderer;
    }

    public abstract void draw();
}

public class Circle extends Shape {
    private final double radius;

    public Circle(Renderer renderer, double radius) {
        super(renderer);
        this.radius = radius;
    }

    public void draw() {
        renderer.renderCircle(radius);
    }
}

// Usage
Shape cv = new Circle(new VectorRenderer(), 5.0);
cv.draw();

Explanation

The Bridge Pattern separates two dimensions into two class hierarchies:

  • Abstraction (Shape): Defines the high-level interface clients interact with
  • Implementation (Renderer): Defines the low-level operations that carry out the work

The abstraction holds a reference to the implementation and delegates work to it. You can add new shapes (e.g., Square) or new renderers (e.g., SVGRenderer) without modifying existing code.

Variants

VariantDescriptionUse Case
Classic BridgeTwo parallel hierarchiesShapes and renderers, devices and drivers
Driver BridgeAbstraction over hardware/OS APIsCross-platform UI frameworks
Remote BridgeLocal abstraction over remote implementationRPC stubs and proxies

Best Practices

  • Identify independent dimensions before applying the pattern — not every multi-hierarchy problem needs a bridge
  • Keep the implementation interface minimal — only expose what the abstraction needs
  • Favor composition over inheritance — the bridge is fundamentally about composition
  • Use dependency injection to wire implementations into abstractions
  • Document which class plays which role (abstraction vs. implementation) for maintainers

Common Mistakes

  • Applying the bridge when a simple strategy or adapter would suffice
  • Making the implementation interface too broad, coupling it unnecessarily to the abstraction
  • Allowing the abstraction to leak implementation details to clients
  • Creating deep hierarchies on both sides, reintroducing the complexity the bridge was meant to solve

Frequently Asked Questions

Q: What is the difference between Bridge and Adapter? A: Adapter makes incompatible interfaces work together. Bridge separates an abstraction from its implementation so both can evolve independently. The intent and structure differ.

Q: When should I use Bridge instead of Strategy? A: Strategy varies a single algorithm. Bridge separates two entire class hierarchies. Use Bridge when you have two independent dimensions of variation.