Decouple an abstraction from its implementation so that the two can vary independently.
AdvancedPattern #07 of 23
01 — Intent
What is the Bridge Pattern?
Bridge splits a large class (or set of closely related classes) into two separate hierarchies — abstraction and implementation — that can be developed and extended independently. The abstraction holds a reference to the implementation and delegates work to it.
Without Bridge, combining M abstractions with N implementations would require M×N subclasses. Bridge reduces this to M+N classes.
Real-world analogy: A TV remote (abstraction) and a TV (implementation). You can have different remotes (basic, advanced) and different TVs (Samsung, Sony). Any remote works with any TV — the remote doesn't know the TV's internals, it just sends signals through a standard interface.
02 — Problem
The Problem It Solves
You have a Shape class with subtypes Circle and Square. You want to add colours — Red and Blue. Using inheritance: RedCircle, BlueCircle, RedSquare, BlueSquare — 4 classes for 2 shapes × 2 colours. Add one more shape and one more colour and you have 9 classes. This explodes exponentially.
Bridge solves this by separating the Shape hierarchy from the Color hierarchy and connecting them via composition.
03 — Solution
Participants
Abstraction
High-level control layer. Holds a reference to an Implementor and delegates the actual work to it. Defines the interface clients use.
RefinedAbstraction
Extends Abstraction with optional variations. Adds higher-level operations on top of the base abstraction.
Implementor (interface)
Defines the interface for implementation classes. Does not have to match the Abstraction interface — typically lower-level primitive operations.
ConcreteImplementor
Provides platform-specific or variant-specific implementations of the Implementor interface.
04 — Structure
Visual Flow Diagram
05 — Implementation
Java Code Example
Java
// Implementor interface — lower-level rendering operationspublic interfaceColor {
voidapplyColor();
}
// Concrete Implementorspublic classRedColorimplementsColor {
public voidapplyColor() { System.out.println("Applying RED color"); }
}
public classBlueColorimplementsColor {
public voidapplyColor() { System.out.println("Applying BLUE color"); }
}
public classGreenColorimplementsColor {
public voidapplyColor() { System.out.println("Applying GREEN color"); }
}
// Abstraction — holds reference to Implementor (the bridge)public abstract classShape {
protectedColor color; // THE BRIDGE — composition over inheritancepublicShape(Color color) { this.color = color; }
public abstract voiddraw();
}
// Refined Abstractions — vary independently of Colorpublic classCircleextendsShape {
private int radius;
publicCircle(int radius, Color color) {
super(color);
this.radius = radius;
}
public voiddraw() {
System.out.print("Circle radius=" + radius + " — ");
color.applyColor(); // delegates to implementor
}
}
public classSquareextendsShape {
private int side;
publicSquare(int side, Color color) {
super(color);
this.side = side;
}
public voiddraw() {
System.out.print("Square side=" + side + " — ");
color.applyColor();
}
}
// Client — combines any shape with any color freelypublic classMain {
public static voidmain(String[] args) {
Shape redCircle = newCircle(10, newRedColor());
Shape blueSquare = newSquare(5, newBlueColor());
Shape greenCircle = newCircle(7, newGreenColor());
redCircle.draw(); // Circle radius=10 — Applying RED color
blueSquare.draw(); // Square side=5 — Applying BLUE color
greenCircle.draw(); // Circle radius=7 — Applying GREEN color// Switch implementor at runtime — the bridge flexes!
redCircle.color = newBlueColor();
redCircle.draw(); // same circle, now blue
}
}
06 — Applicability
When to Use / Avoid
✓ Use When
You want to avoid a permanent binding between abstraction and implementation
Both abstractions and implementations should be extensible via subclassing
You have a class explosion from combining two independent dimensions
You want to switch implementations at runtime
Implementation changes should have no impact on client code
✕ Avoid When
Only one implementation exists — unnecessary abstraction layer
The abstraction and implementation are tightly coupled by nature
Simple inheritance clearly solves the problem without class explosion
07 — Real World
Real-World Examples
Java AWT — Component (abstraction) bridges to platform-specific ComponentPeer (implementor) for Windows/Mac/Linux rendering
JDBC — Connection, Statement are abstractions; each DB driver provides the concrete implementor
Spring's PlatformTransactionManager — transaction abstraction over JDBC, JPA, Hibernate implementors
08 — Trade-offs
Pros & Cons
Pros
Eliminates M×N class explosion — reduces to M+N classes
Open/Closed — add new abstractions and implementations independently
Runtime binding — swap implementor without changing abstraction
Single Responsibility — abstraction and implementation each focused
Cons
Increased complexity for simple scenarios — overkill if only one or two combinations needed
Harder to understand than plain inheritance — requires upfront design insight
Indirection overhead — every call goes through the bridge
09 — Break & Prevent
How Bridge Can Be Broken
⚠ Attack Vectors
Downcasting the implementor: Abstraction casts the Implementor to a specific ConcreteImplementor to call methods not in the interface — shatters the decoupling
Null implementor: Abstraction's impl field is null when draw() is called — NullPointerException with no clear error message
Public implementor field: If the impl reference is public, clients replace it with an incompatible object at runtime causing unexpected behaviour
Fat Implementor interface: Adding too many methods to the Implementor interface means every ConcreteImplementor must implement them all — partial implementations with empty bodies silently break the contract
Cross-dimension dependencies: A RefinedAbstraction references a specific ConcreteImplementor by type — re-introduces the tight coupling Bridge was designed to eliminate
✓ Prevention
Never downcast the implementor: The Abstraction must only call methods defined on the Implementor interface — zero knowledge of concrete type
Guard against null: Validate the implementor in the constructor or provide a NullObject default — never allow a null bridge reference
Keep implementor field protected/private: Expose it only through a setter with validation so external code cannot corrupt the bridge
Keep Implementor interface lean: Only primitive operations that all implementations genuinely support. Use multiple Implementor interfaces if needed rather than one fat one
Inject the implementor: Use constructor injection so the bridge is set at creation time — prevents partial or invalid states
Java — Break & Fix
// ❌ BREAKING — downcasting implementorpublic voiddraw() {
((RedColor) color).applySpecialEffect(); // ❌ tied to concrete impl
}
// ✅ FIX — only call interface methodspublic voiddraw() {
color.applyColor(); // ✅ only Implementor interface methods
}
// ❌ BREAKING — null implementorShape broken = newCircle(5, null); // NPE on draw()// ✅ FIX — validate in constructorpublicShape(Color color) {
if (color == null)
throw newIllegalArgumentException("Color implementor required");
this.color = color;
}
// ✅ OR — use NullObject pattern as safe defaultpublic classNoColorimplementsColor {
public voidapplyColor() { /* intentional no-op safe default */ }
}
// ❌ BREAKING — public bridge field, client corrupts itpublicColor color; // client sets incompatible object// ✅ FIX — protected with validated setterprotectedColor color;
public voidsetColor(Color c) {
if (c == null) throw newIllegalArgumentException("null not allowed");
this.color = c;
}
10 — Related Patterns
How Other Patterns Relate
Adapter
Often Confused
Adapter is a retrofit — it reconciles incompatible interfaces that already exist. Bridge is designed upfront to separate two dimensions that should vary independently. Adapter fixes; Bridge plans.
Strategy
Often Confused
Bridge and Strategy both use composition to delegate behaviour. Strategy focuses on swapping algorithms at runtime inside one class. Bridge separates entire class hierarchies across two dimensions.
Abstract Factory
Often Used Together
Abstract Factory can create the correct ConcreteImplementor for a Bridge, hiding the creation logic so the Abstraction never needs to know which implementor it receives.
Template Method
Competes With
Template Method uses inheritance to vary parts of an algorithm; Bridge uses composition to vary entire implementations. Bridge is more flexible — implementations can be swapped at runtime.
11 — Quick Recap
Interview Cheat Sheet
What: Separates two independent dimensions (abstraction + implementation) into separate hierarchies connected via composition — eliminates M×N subclass explosion.
How: Abstraction holds a reference to an Implementor interface. RefinedAbstractions extend Abstraction. ConcreteImplementors implement the Implementor interface. Client sets the implementor at construction or runtime.
Key interview distinction: Adapter = retrofit (fixes existing incompatibility). Bridge = upfront design (prevents future class explosion). Both use composition but serve opposite purposes.