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.
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:
That way, adding a new delivery method does not require rewriting every report abstraction.
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.
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:
Use protocols when the main variation is concrete behavior attached to a type.
These patterns are easy to confuse.
Adapter is a boundary-conversion story. Bridge is an architecture-of-variation story.
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:
The implementation side should own format, transport, encoding, or other strategy details.
If the bridge introduces too many layers before real variation exists, the code gets ceremonial.
If the abstraction still contains knowledge of every implementation branch, the bridge did not actually decouple anything.
Sometimes a simple function parameter or configuration map is enough. Bridge is for repeated independent variation, not for every small option.
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.