Bridge


If you’ve ever started sketching out a class hierarchy and realized halfway through that you’re about to multiply two unrelated things together, this is for you. The vehicle workshop example here makes it concrete: four vehicle types, four workshop operations, and if you inherit your way through it you end up writing CarProduce, CarAssemble, BikeProduce, BikeAssemble, and so on until you’ve written sixteen classes for what’s really two independent lists of four things each.

The problem

You’ve got two dimensions of variation that both need to grow independently. Every time inheritance is the tool you reach for in this situation, you get a class per combination, and every new vehicle type or every new workshop operation multiplies the class count instead of adding to it.

Without the pattern

The instinct that gets you into trouble is folding both axes into one inheritance tree. Say you skip the Workshop interface entirely and just give Vehicle an abstract performOperation(), letting each vehicle type override it per operation it needs. Now Car needs a produce step, an assemble step, a paint step, and an inspect step, and since a single override can’t cleanly be “four different methods depending on context,” you end up carving out a subclass per operation instead: CarProduce, CarAssemble, CarPaint, CarInspect. Fine, that’s one vehicle handled. Then Bike needs the same four operations, so you write BikeProduce, BikeAssemble, BikePaint, BikeInspect, and by the time Truck and Motorcycle show up you’ve written sixteen classes to express what’s really a 4-by-4 grid of two independent lists.

classDiagram
    class Vehicle {
        <<abstract>>
    }
    Vehicle <|-- CarProduce
    Vehicle <|-- CarAssemble
    Vehicle <|-- CarPaint
    Vehicle <|-- CarInspect
    Vehicle <|-- BikeProduce
    Vehicle <|-- BikeAssemble
    Vehicle <|-- BikePaint
    Vehicle <|-- BikeInspect
    Vehicle <|-- TruckProduce
    Vehicle <|-- TruckAssemble
    Vehicle <|-- TruckPaint
    Vehicle <|-- TruckInspect
    Vehicle <|-- MotorcycleProduce
    Vehicle <|-- MotorcycleAssemble
    Vehicle <|-- MotorcyclePaint
    Vehicle <|-- MotorcycleInspect

The tell isn’t the number sixteen itself, it’s what happens when either axis grows by one. Add a fifth vehicle, Bus, and you’re not writing one new class, you’re writing four, one per existing operation. Add a fifth operation, Repair, and you’re not writing one new class either, you’re writing four, one per existing vehicle. The two axes were never actually coupled, a workshop doesn’t care what it’s servicing and a vehicle doesn’t care how it’s serviced, but jamming them into a single hierarchy makes every future addition on either side pay for both.

With the pattern

Workshop is the implementor interface: work() and getWorkshopType(). Produce, Assemble, Paint, and Inspect are the concrete implementors, each just printing what it does and naming itself.

Vehicle is the abstraction, an abstract class holding two Workshop references as protected fields, workshop1 and workshop2, set through the constructor. Its manufacture() method is a small template method: print a start message, call performWorkshop1(), call performWorkshop2(), print a completion message. Those two performWorkshopN() methods just delegate to workshop1.work() and workshop2.work(). Vehicle also declares getVehicleType() as abstract, so Car, Bike, Truck, and Motorcycle only need to implement that one method, they inherit everything else.

The key decision is that Vehicle holds Workshop by composition, not by extending some CarWorkshop base. Any vehicle can be constructed with any pair of workshops, new Car(produce, assemble) and new Car(paint, inspect) are both valid without adding a single class. The vehicle hierarchy and the workshop hierarchy know nothing about each other beyond the interface, so you can extend either one without touching the other. Add a Bus vehicle, it works with all four existing workshops immediately. Add a Repair workshop, all four existing vehicles can use it immediately.

classDiagram
    class Workshop {
        <<interface>>
        +work()
        +getWorkshopType() String
    }
    class Produce {
        +work()
        +getWorkshopType() String
    }
    class Assemble {
        +work()
        +getWorkshopType() String
    }
    class Paint {
        +work()
        +getWorkshopType() String
    }
    class Inspect {
        +work()
        +getWorkshopType() String
    }
    class Vehicle {
        <<abstract>>
        #workshop1: Workshop
        #workshop2: Workshop
        +manufacture()
        #performWorkshop1()
        #performWorkshop2()
        +getVehicleType()* String
        +displayConfiguration()
    }
    class Car {
        +getVehicleType() String
    }
    class Bike {
        +getVehicleType() String
    }
    class Truck {
        +getVehicleType() String
    }
    class Motorcycle {
        +getVehicleType() String
    }
    Workshop <|.. Produce
    Workshop <|.. Assemble
    Workshop <|.. Paint
    Workshop <|.. Inspect
    Vehicle <|-- Car
    Vehicle <|-- Bike
    Vehicle <|-- Truck
    Vehicle <|-- Motorcycle
    Vehicle o-- Workshop

What it costs you

Bridge fixes the explosion, but it isn’t free. You’re now maintaining two hierarchies instead of one, Vehicle and its subclasses on one side, Workshop and its subclasses on the other, and reasoning about a Car built with Paint and Inspect means holding both trees in your head at once instead of just one. Every manufacture() call also pays an indirection hop the naive version didn’t have: performWorkshop1() doesn’t do the work itself, it calls workshop1.work() through a field reference, so you’re chasing composition instead of hitting a method directly on the object. And it’s premature if the implementation side never actually varies, if every vehicle in your system always gets the same four workshop steps in the same order, splitting Workshop out as its own hierarchy just gives you two class trees to update every time you touch what used to be one thing. Bridge earns its cost when both axes genuinely move independently, not because two hierarchies sounds more architecturally sound than one.

When to reach for it

  • You have two hierarchies that both want to grow, and inheriting through both at once multiplies your class count.
  • You want to pick or swap the implementation side at runtime, not bake it in at compile time.
  • You’re designing this upfront, this isn’t a retrofit pattern, that’s Adapter’s job.
  • The implementation side is a set of interchangeable single things, not a matched family of objects. If it’s a family (all-Motif widgets, all-Mac widgets that must stay consistent), that’s Abstract Factory’s job instead, the two get compared in Designing a Document Editor.

The takeaway

Bridge is what you reach for before the class explosion happens, not after. If you’re already staring at a naming scheme like CarProduce and BikeAssemble, that’s the signal you needed this two designs ago.

Read the full source on GitHub.

← Back to Structural Patterns

Want to get blog posts over email?

Enter your email address and get notified when there's a new post!

Comments