Bridge Pattern for Separating Abstraction in Clojure

Learn how to separate what an operation means from how it is executed so Clojure systems can evolve behavior and implementation strategies independently.

Bridge pattern: A structural pattern that separates an abstraction from the implementation strategies behind it so the two can change independently.

Bridge is useful when two dimensions of variation would otherwise create a combinatorial mess. One dimension might be what the system does, and the other might be how it performs that work. In Clojure, that often looks like keeping domain-level operations separate from transport, rendering, storage, or execution details.

Think in Dimensions of Change

Suppose a system needs to generate reports in multiple formats and deliver them through multiple channels. If every format-channel combination becomes its own special path, the system grows awkwardly.

Bridge says:

  • keep the domain abstraction small and stable
  • keep implementation strategies behind a separate seam
  • combine them at composition time

That way, adding a new delivery method does not require rewriting every report abstraction.

Protocols Are a Natural Bridge Tool

Protocols work well when the implementation side needs a stable behavior contract.

 1(defprotocol ReportRenderer
 2  (render-report [renderer report-data]))
 3
 4(defrecord HtmlRenderer []
 5  ReportRenderer
 6  (render-report [_ report-data]
 7    (str "<h1>" (:title report-data) "</h1>")))
 8
 9(defrecord JsonRenderer []
10  ReportRenderer
11  (render-report [_ report-data]
12    {:title (:title report-data)
13     :summary (:summary report-data)}))
14
15(defn publish-report [renderer report-data]
16  (render-report renderer report-data))

Here, the abstraction is “publish a report.” The implementation detail is how it is rendered.

Multimethods Help When Dispatch Depends on More Than Type

If the implementation depends on several attributes, such as :channel and :compliance-tier, multimethods can express the bridge more naturally than one protocol alone.

Use multimethods when:

  • dispatch is data-driven
  • one type can support several strategies
  • the branching logic belongs in configuration or domain data

Use protocols when the main variation is concrete behavior attached to a type.

Bridge Is Different from Adapter

These patterns are easy to confuse.

  • adapter makes incompatible interfaces fit together
  • bridge keeps two evolving dimensions from being hard-wired to each other

Adapter is a boundary-conversion story. Bridge is an architecture-of-variation story.

Good Bridge Design Keeps the Domain Side Small

A weak bridge often creates elaborate abstractions that are harder to understand than the original problem. The abstraction should stay close to the business meaning:

  • publish a report
  • store a document
  • send a notification
  • evaluate a rule

The implementation side should own format, transport, encoding, or other strategy details.

Common Failure Modes

Building Both Sides Too Abstractly

If the bridge introduces too many layers before real variation exists, the code gets ceremonial.

Mixing Strategy Logic Back into the Abstraction

If the abstraction still contains knowledge of every implementation branch, the bridge did not actually decouple anything.

Using Bridge for a One-Off Variation

Sometimes a simple function parameter or configuration map is enough. Bridge is for repeated independent variation, not for every small option.

Practical Heuristics

Use a bridge when two different kinds of change are forcing each other to grow together. In Clojure, protocols and multimethods are usually enough to express that split cleanly. Keep the abstraction business-oriented, keep the implementation seam narrow, and do not build more indirection than the real variation requires.

Ready to Test Your Knowledge?

Loading quiz…
Revised on Thursday, April 23, 2026