Home Design Patterns Bridge

Bridge

Structural Gang of Four

“Decouple an abstraction from its implementation so that the two can vary independently.”

Gamma, Helm, Johnson & Vlissides, p. 151

Purpose

A Bridge expands on the service provided by inheritance, which lets multiple versions of an object be treated individually or similarly as needed. A Bridge adds an additional layer between the abstraction (superclass) and its implementation (subclasses).

Typically, if you change the superclass in an inheritance tree, all of the subclasses have to change as well. A Bridge lets these two change without that tight coupling, and lets it happen dynamically at runtime.

Design

The Bridge uses aggregation and inheritance to separate responsibilities into different sets of classes. Rather than everything inheriting from a single parent, the Bridge pattern breaks the problem into two inheritance trees.

This is useful when the tree wants to change in two dimensions at once. Alex at Mastering Software Development explains it with an example that finally made the pattern click for me: it turns this…

One root class, three abstract scheduling subclasses, and nine platform-specific leaf classes beneath them
Thirteen classes, because there is one leaf per combination. Adding a fourth platform means adding three more classes, one under each scheduling type.

…into this:

Two separate class trees, ThreadScheduling and ThreadAccess, joined by a single reference
Eight classes, and the count now grows by addition rather than multiplication. A fourth platform is one new class on the right, and nothing on the left has to know about it.

This is done by moving the platform-specific Linux, Windows and Mac classes together on one side, separating them from the three types of scheduling which are common to all of those platforms.

UML class diagram: Abstraction holds a reference to Implementor; each has its own subclass hierarchy.
The generic arrangement. The single impl reference is the bridge, and it is the only place the two hierarchies touch.

Example

Autobot Robotics has several functions it requires its robotic arms to perform: using a drill to cut holes in a circuit board, placing a completed board into a box, and cleaning up the workspace when it gets too dirty. However, it wants the system to do all of this using three different robotic arm systems.

UML class diagram: ArmApplications holds an ArmController; three task subclasses on the left, three arm models on the right.
Tasks on one side, arm hardware on the other. Three tasks and three arms is six classes here. Flattened into one hierarchy it would be nine.
class client {
    public void start() {
        ArmApplications aa = new ArmApplications(new Arm601());
        aa.doActivity();
    }
}

class ArmApplications {
    protected ArmController localController;
    public ArmApplications(ArmController ac) { localController = ac; }
    public void doActivity();
}

class DrillHoles : ArmApplications {
    public void doActivity() {
        localController.moveArm();
    }
}

class PutInBox : ArmApplications { }   // same thing as DrillHoles
class Cleaner  : ArmApplications { }   // same thing as DrillHoles

class ArmController {
    public void moveArm();
}

class Arm601 : ArmController {
    public void moveArm() {
        // Do movement on specific arm type
    }
}

class Arm802  : ArmController { }   // same thing as Arm601
class Arm1210 : ArmController { }   // same thing as Arm601
The implementation. The whole pattern is the localController field and the constructor that fills it: one reference, chosen at runtime, joining the two hierarchies.

Comparisons

  • Adapter also stands between two sides, but it is applied after the fact to code you did not design. A Bridge is planned in advance. Same shape, opposite intent.
  • Abstract Factory pairs naturally with a Bridge. It is a good way to decide which Implementor an Abstraction gets handed.
  • Strategy looks nearly identical in UML. The difference is what varies: Strategy swaps an algorithm, Bridge swaps an entire platform or implementation, and Bridge expects both sides to keep growing.

Why It Exists

My take

Bridge exists because inheritance multiplies. The moment a design varies along two independent axes, single inheritance forces you to pick one axis for the tree and encode the other by hand. The class count then becomes the product of the two rather than the sum. Three platforms and three scheduling types is nine classes. Add a fourth platform and it is twelve. Nobody decides to build that; you arrive at it one reasonable subclass at a time.

What makes Bridge hard to recognize is that the pain shows up long after the mistake. The first two subclasses are fine. The design only becomes obviously wrong at around the sixth, by which point rearranging the hierarchy touches everything. So the value of knowing this pattern is less about implementing it and more about spotting the second axis early, while the tree is still small enough to split cheaply.

I would also argue the name is doing the pattern a disservice. "Bridge" describes the connecting reference, which is the least interesting part. The actual idea is the deliberate refusal to let two unrelated reasons for change share one inheritance tree.

Criticisms

The Bridge solves a very specific type of problem. When it is needed, it is usually needed badly. However, it is an unusual pattern to actually need to implement, and reaching for it before the second axis of variation genuinely exists just buys you an extra layer of indirection for nothing.