Skip to content

Bridge Pattern — Decouple Abstraction from Implementation

DodaTech Updated 2026-06-24 4 min read

In this tutorial, you'll learn about Bridge Pattern. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

What You'll Learn

You will understand how the Bridge pattern prevents an explosion of class combinations by separating an object's high-level operations from their platform-specific implementations. You will distinguish Bridge from Adapter, which is similar in structure but different in intent.

Why It Matters

Without Bridge, adding a new platform or a new abstraction forces you to subclass across both dimensions. A classic example: shapes and renderers — adding Circle and SVGRenderer shouldn't require creating CircleSVGRenderer, SquareSVGRenderer, CircleCanvasRenderer, and so on. With 5 shapes and 4 renderers, you'd need 20 classes. Bridge reduces this to 9 classes (5 shapes + 4 renderers) by letting them vary independently.

Real-World Use

Cross-platform graphics libraries (Skia, Cairo, Direct2D) expose a uniform drawing API backed by platform-specific implementations. DodaTech's document renderer separates document models (PDF, HTML, Markdown) from output backends (file, S3, HTTP response). Adding a new output format like JSON doesn't require touching any document model, and adding a new document template doesn't require touching any output backend.

The Pattern

Abstraction defines the high-level control logic. Implementor declares the platform-specific interface. ConcreteImplementor provides the platform binding. RefinedAbstraction extends the abstraction.

from abc import ABC, abstractmethod

class Renderer(ABC):
    @abstractmethod
    def render_circle(self, x: float, y: float, r: float) -> str:
        pass

    @abstractmethod
    def render_square(self, x: float, y: float, side: float) -> str:
        pass

class SVGRenderer(Renderer):
    def render_circle(self, x: float, y: float, r: float) -> str:
        return f'<circle cx="{x}" cy="{y}" r="{r}" />'

    def render_square(self, x: float, y: float, side: float) -> str:
        return f'<rect x="{x}" y="{y}" width="{side}" height="{side}" />'

class CanvasRenderer(Renderer):
    def render_circle(self, x: float, y: float, r: float) -> str:
        return f"Canvas: circle at ({x},{y}) radius {r}"

    def render_square(self, x: float, y: float, side: float) -> str:
        return f"Canvas: square at ({x},{y}) size {side}"

class Shape(ABC):
    def __init__(self, renderer: Renderer):
        self._renderer = renderer

    @abstractmethod
    def draw(self) -> str:
        pass

class Circle(Shape):
    def __init__(self, renderer: Renderer, x: float, y: float, r: float):
        super().__init__(renderer)
        self._x = x
        self._y = y
        self._r = r

    def draw(self) -> str:
        return self._renderer.render_circle(self._x, self._y, self._r)

class Square(Shape):
    def __init__(self, renderer: Renderer, x: float, y: float, side: float):
        super().__init__(renderer)
        self._x = x
        self._y = y
        self._side = side

    def draw(self) -> str:
        return self._renderer.render_square(self._x, self._y, self._side)
svg = SVGRenderer()
canvas = CanvasRenderer()
shapes = [Circle(svg, 10, 10, 5), Square(canvas, 0, 0, 20)]
for s in shapes:
    print(s.draw())
<circle cx="10" cy="10" r="5" />
Canvas: square at (0,0) size 20

Structure

classDiagram
    class Abstraction {
        #implementor: Implementor
        +operation()
    }
    class RefinedAbstraction {
        +operation()
    }
    class Implementor {
        <>
        +operationImpl()
    }
    class ConcreteImplementorA {
        +operationImpl()
    }
    class ConcreteImplementorB {
        +operationImpl()
    }
    RefinedAbstraction --|> Abstraction
    ConcreteImplementorA ..|> Implementor
    ConcreteImplementorB ..|> Implementor
    Abstraction --> Implementor : delegates

Real-World Usage

  • Java JDBC — the DriverManager abstraction works with any database driver implementation (MySQL, PostgreSQL, H2).
  • Python logging handlers — loggers (abstraction) send output to handlers (implementation) like FileHandler, StreamHandler, SysLogHandler.
  • React NativeJavaScript components (abstraction) render via platform-specific threads (implementation).
  • Qt QPainter — painting abstraction runs on raster, OpenGL, or PDF backends.
  • Adapter makes unrelated classes work together; Bridge is designed up-front.
  • Abstract Factory can create and configure a particular Bridge implementation.
  • Strategy is similar but focuses on algorithms rather than platform-binding.
  • Composite can be combined with Bridge when tree structures need platform-specific rendering.

Pros and Cons

Pros Cons
Eliminates combinatorial class explosion Increases complexity if the abstraction is stable
Implementation details stay isolated Requires upfront design — hard to retrofit
Both hierarchies can be extended independently Indirect call adds a small runtime cost
Follows the Open/Closed Principle perfectly Can feel like over-engineering for single-platform apps

The code demonstrates the combinatorial savings. With 2 shapes (Circle, Square) and 2 renderers (SVG, Canvas), the Bridge approach requires only 4 shape classes and 2 renderer classes = 6 total. The non-Bridge approach (one class per shape-renderer combination) would require 4 classes already, and the gap widens as dimensions grow.

Practice Questions

  1. How does Bridge differ from Adapter in terms of when each pattern is applied?
  2. Implement a messaging bridge where the abstraction is a Notification and implementations are EmailSender, SMSSender, and PushSender.
  3. What happens to the Bridge when both sides have more than one dimension of variation? How does the class count scale?
  4. How would you configure a Bridge using Dependency Injection to select implementations at runtime?

Challenge

Add a third implementor — AsciiRenderer that renders shapes using ASCII art characters. Verify it works with both Circle and Square without modifying either class.

Real-World Task

Identify two orthogonal dimensions of variation in your architecture (e.g., data format × storage backend, or notification type × delivery channel). Use DodaTech's variant explorer to map them and refactor with Bridge.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro