Design PatternsAdvanced.17 min read

Bridge Pattern

Decouple an abstraction from its implementation so both hierarchies can vary independently without a combinatorial explosion of subclasses.

StructuralJavaCompositionExtensibility
Bridge Pattern infographic showing Shape abstraction connected to Renderer implementor with Circle, Rectangle, OpenGL and DirectX variants
The supplied Bridge diagram separates the shape hierarchy from the renderer hierarchy through a has-a reference.

Concept Overview

The Bridge pattern separates an abstraction from its implementation and connects them through composition. It is useful when two independent dimensions of variation would otherwise create a large inheritance hierarchy. The supplied infographic uses shapes and rendering technologies: Circle and Rectangle vary as abstractions, while OpenGLRenderer and DirectXRenderer vary as implementors. Each shape can use either renderer without requiring a dedicated subclass for every combination.

1. The problem: subclass combinations grow quickly

Suppose an application supports multiple shapes and multiple rendering technologies. If each shape subclass is tied directly to one renderer, adding a new shape or renderer requires more combination classes. With m shapes and n renderers, the number of combinations can approach m × n. The result is a rigid hierarchy where changes in one dimension force changes in another.

2. Separate abstraction from implementor

The Abstraction defines the high-level operations and holds a reference to an Implementor. Refined Abstractions extend the high-level concept, while Concrete Implementors provide the low-level implementation. In the example, Shape has a Renderer, Circle and Rectangle are refined abstractions, and OpenGLRenderer and DirectXRenderer are concrete implementors. Both hierarchies can be extended independently.

Bridge structurejava
interface Renderer {
    void render(String shape);
}

abstract class Shape {
    protected final Renderer renderer;

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

    public abstract void draw();
}

final class Circle extends Shape {
    Circle(Renderer renderer) { super(renderer); }

    public void draw() {
        renderer.render("Circle");
    }
}

3. Why composition is the bridge

The abstraction holds an implementor reference rather than inheriting a concrete renderer. The client creates a shape with the desired renderer and invokes the abstraction's operation. This is a has-a relationship. The shape decides the high-level operation; the renderer supplies the platform-specific mechanism. The Bridge pattern is therefore more than using an interface: it is deliberately separating two independently varying dimensions.

4. Extend both sides independently

A new Triangle abstraction can use an existing renderer without creating TriangleOpenGLRenderer and TriangleDirectXRenderer classes. Likewise, a new VulkanRenderer can work with existing shapes. This reduces the number of combination classes and keeps each hierarchy focused on one axis of change. The cost is an additional abstraction and indirection, so Bridge is most valuable when both dimensions really do vary independently.

5. When to use Bridge

Use Bridge when an abstraction and its implementation should be independently extensible, when a hierarchy is producing many combination subclasses, or when platform-specific implementations need to sit behind a stable domain-facing API. It can be useful in rendering, database drivers, device control and cross-platform UI layers. Do not introduce it merely to avoid one or two subclasses if the dimensions are unlikely to change independently.

Key Takeaways

  • ✓Bridge separates two dimensions of variation into independent hierarchies.
  • ✓The abstraction contains a reference to an implementor through composition.
  • ✓New abstractions and implementations can be added independently.
  • ✓Use it when inheritance combinations are becoming difficult to maintain.