Structural pattern · Gang of Four · Advanced

Bridge

Decouple an abstraction from its implementation so the two can vary independently, instead of multiplying into N×M types.

Structural Advanced Complete

📺 Analogy

A TV remote (the abstraction) and the TV itself (the implementation) evolve separately. A fancy universal remote works with a Sony or a Samsung; a basic remote works with both too. You don’t build a SonyFancyRemote and a SamsungBasicRemote for every pairing — the remote talks to any TV through a shared interface.

The problem

Two things vary at once: what you’re drawing (circle, square) and how it’s rendered (vector, ASCII). Model them with inheritance and you need a type for every combination — CircleVector, CircleASCII, SquareVector… an N×M explosion. Bridge separates the two hierarchies: the abstraction holds a reference to an implementation interface, so each side grows on its own.

Structure

classDiagram
  class Circle {
    -renderer Renderer
    -radius float64
    +Draw()
    +Resize()
  }
  class Renderer {
    <<interface>>
    +RenderCircle(radius)
  }
  class VectorRenderer { +RenderCircle() }
  class ASCIIRenderer { +RenderCircle() }
  Circle o--> Renderer : bridges to
  Renderer <|.. VectorRenderer
  Renderer <|.. ASCIIRenderer

Idiomatic Go

In Go, Bridge is just composition: the abstraction holds the implementation as an interface field. Any shape pairs with any renderer — no combinatorial types. Edit and Run:

package main

import "fmt"

// Implementor: the "how it's rendered" dimension.
type Renderer interface {
	RenderCircle(radius float64) string
}

type VectorRenderer struct{}

func (VectorRenderer) RenderCircle(r float64) string {
	return fmt.Sprintf("vector circle, radius %.1f", r)
}

type ASCIIRenderer struct{}

func (ASCIIRenderer) RenderCircle(r float64) string {
	return fmt.Sprintf("ascii circle, ~%d chars wide", int(r*2))
}

// Abstraction: the "what it is" dimension, bridged to a Renderer.
type Circle struct {
	renderer Renderer
	radius   float64
}

func (c Circle) Draw() string      { return c.renderer.RenderCircle(c.radius) }
func (c *Circle) Resize(f float64) { c.radius *= f }

func main() {
	// any shape × any renderer — no CircleVector / CircleASCII explosion
	for _, r := range []Renderer{VectorRenderer{}, ASCIIRenderer{}} {
		c := Circle{renderer: r, radius: 5}
		c.Resize(2)
		fmt.Println(c.Draw())
	}
}

🐹 Bridge is almost invisible in Go

Because Go pushes you toward “composition over inheritance,” Bridge often appears without anyone naming it — you just hold an interface field. The skill is recognizing the two independent dimensions early and refusing to let them multiply. It resembles Strategy (inject behavior) but Strategy swaps an algorithm, while Bridge separates a whole abstraction hierarchy from an implementation hierarchy.

In the standard library

  • io.Writer bridges writers (fmt.Fprintf, loggers) from concrete sinks (file, buffer, socket).
  • database/sql bridges the SQL API from pluggable drivers.
  • log/slog bridges the logging front-end from its Handler back-ends.

Pitfalls

⚠️ Don't bridge a single dimension

If only one axis ever varies, a Bridge is over-design — a plain interface (or Strategy) says it better. The pattern earns its complexity only when both sides genuinely change independently. Introduce it when the second dimension actually appears, not before.

When to use it — and when not

✅ Reach for it when

  • Two (or more) dimensions vary independently — e.g. shape × renderer, message × transport.
  • You'd otherwise get a class explosion (CircleSVG, CircleCanvas, SquareSVG, …).
  • You want to swap the implementation at runtime.

⛔ Think twice when

  • Only one dimension actually varies — a single interface (or Strategy) is enough.
  • The abstraction and implementation never change independently.

Check your understanding

Score: 0 / 3

1. What does Bridge decouple?

Bridge splits a design along two axes and connects them with an interface, so you can combine any abstraction with any implementation without subclassing every pairing.

2. What explosion does Bridge prevent?

Without Bridge, M shapes × N renderers means M×N concrete types. With Bridge it's M + N — each side grows independently.

3. How does Bridge differ from Adapter?

Adapter is reactive — make X fit interface Y after the fact. Bridge is proactive design — keep the abstraction and implementation apart so both can evolve.