0

Bridge — separating abstraction from implementation

Advanced5 min read·eng-03-006
compare

Concept

Bridge pattern separates an abstraction from its implementation so that the two can vary independently. Instead of a single inheritance hierarchy doing double duty (abstracting AND implementing), Bridge splits them into two separate hierarchies connected by composition.

The problem without Bridge: You have Shape (Circle, Square) and rendering backends (OpenGL, DirectX, SVG). Combining them with inheritance creates: OpenGLCircle, DirectXCircle, SVGCircle, OpenGLSquare, DirectXSquare, SVGSquare — 6 classes for 3×2. Add a new shape and renderer, and the count explodes. Bridge solves this with composition: separate the shape hierarchy from the renderer hierarchy, connect with a reference.

Structure:

  • Abstraction (Shape): The high-level control. Has a reference to ImplementorInterface.
  • Refined Abstraction (Circle, Square): Extends Abstraction.
  • Implementor Interface (RendererInterface): Implementation interface (distinct from Abstraction's interface).
  • Concrete Implementor (OpenGLRenderer, SVGRenderer): Concrete implementations.

Key insight: The "bridge" is the reference from Abstraction to Implementor. Abstractions delegate to Implementors. Two independent hierarchies, coupled through an interface.

Bridge vs Strategy: Both use composition and an interface. Bridge is about splitting a class that does two independent things into separate hierarchies. Strategy is about making one behavior swappable. Bridge is a structural pattern (about structure); Strategy is behavioral (about behavior at runtime).

Real-world: Database abstraction layers (PDO bridges PHP code to MySQL, PostgreSQL, SQLite). UI toolkits. Notification systems (notification type × delivery channel).

Code Example

php
<?php
// Implementor hierarchy — rendering backends
interface RendererInterface
{
    public function renderCircle(float $x, float $y, float $radius): string;
    public function renderSquare(float $x, float $y, float $side): string;
}

class SVGRenderer implements RendererInterface
{
    public function renderCircle(float $x, float $y, float $radius): string
    {
        return "<circle cx='{$x}' cy='{$y}' r='{$radius}' />";
    }

    public function renderSquare(float $x, float $y, float $side): string
    {
        return "<rect x='{$x}' y='{$y}' width='{$side}' height='{$side}' />";
    }
}

class CanvasRenderer implements RendererInterface
{
    public function renderCircle(float $x, float $y, float $radius): string
    {
        return "ctx.arc({$x}, {$y}, {$radius}, 0, 2*Math.PI);";
    }

    public function renderSquare(float $x, float $y, float $side): string
    {
        return "ctx.fillRect({$x}, {$y}, {$side}, {$side});";
    }
}

// Abstraction hierarchy — shapes
abstract class Shape
{
    public function __construct(
        protected float $x,
        protected float $y,
        protected readonly RendererInterface $renderer, // THE BRIDGE
    ) {}

    abstract public function render(): string;
    abstract public function area(): float;
}

class Circle extends Shape
{
    public function __construct(
        float $x, float $y,
        private readonly float $radius,
        RendererInterface $renderer,
    ) {
        parent::__construct($x, $y, $renderer);
    }

    public function render(): string { return $this->renderer->renderCircle($this->x, $this->y, $this->radius); }
    public function area(): float    { return M_PI * $this->radius ** 2; }
}

class Square extends Shape
{
    public function __construct(
        float $x, float $y,
        private readonly float $side,
        RendererInterface $renderer,
    ) {
        parent::__construct($x, $y, $renderer);
    }

    public function render(): string { return $this->renderer->renderSquare($this->x, $this->y, $this->side); }
    public function area(): float    { return $this->side ** 2; }
}

// Combine independently — 2 shapes × 2 renderers = 4 combinations, no new classes
$svgCircle   = new Circle(50, 50, 30, new SVGRenderer());
$svgSquare   = new Square(10, 10, 40, new SVGRenderer());
$canvasCircle = new Circle(50, 50, 30, new CanvasRenderer());

echo $svgCircle->render();   // <circle cx='50' cy='50' r='30' />
echo $canvasCircle->render(); // ctx.arc(50, 50, 30, 0, 2*Math.PI);

// Add a new renderer (WebGL) without touching Shape classes
// Add a new shape (Triangle) without touching renderer classes