Fundamentals 14 min read

Bridge Pattern – How to Avoid Multiplicative Class Explosion in Structural Design

The article explains how the Bridge design pattern separates two orthogonal dimensions—such as order type and notification channel—into independent hierarchies, replacing inheritance‑driven class explosion with compositional bridges, and demonstrates its rationale, code, real‑world examples, and differences from similar patterns.

Dabaoshi
Dabaoshi
Dabaoshi
Bridge Pattern – How to Avoid Multiplicative Class Explosion in Structural Design

Problem: multiplicative class explosion

When a concept varies along two independent dimensions (order type and notification channel), modeling each combination with inheritance creates a class for every pair. With three order types and three channels, nine concrete classes are needed; adding a type or channel multiplies the count.

Example inheritance hierarchy shows classes such as NormalOrderSmsNotify, NormalOrderAppNotify, GroupOrderEmailNotify, etc., illustrating the 3×3=9 scenario.

Root cause

Inheritance provides a single linear hierarchy, forcing both dimensions into the same chain and coupling their evolution.

Bridge pattern solution

Separate the dimensions into two independent hierarchies and connect them via composition.

Implementation side defines a NotifyChannel interface with concrete channels.

public interface NotifyChannel {
    void send(String message);
}
class SmsChannel implements NotifyChannel {
    public void send(String m) { /* send SMS */ }
}
class AppChannel implements NotifyChannel {
    public void send(String m) { /* push app */ }
}
class EmailChannel implements NotifyChannel {
    public void send(String m) { /* send email */ }
}

Abstraction side defines an abstract OrderNotify that holds a NotifyChannel reference.

public abstract class OrderNotify {
    protected final NotifyChannel channel;
    public OrderNotify(NotifyChannel channel) { this.channel = channel; }
    public abstract void notifyUser();
}
class NormalOrderNotify extends OrderNotify {
    public NormalOrderNotify(NotifyChannel channel) { super(channel); }
    public void notifyUser() { channel.send("Your order has been created"); }
}
class GroupOrderNotify extends OrderNotify {
    public GroupOrderNotify(NotifyChannel channel) { super(channel); }
    public void notifyUser() { channel.send("Group purchase successful, order generated"); }
}

Runtime composition creates any combination without new subclasses:

OrderNotify n1 = new NormalOrderNotify(new SmsChannel());
n1.notifyUser();
OrderNotify n2 = new GroupOrderNotify(new EmailChannel());
n2.notifyUser();

Class count drops from 9 to 6 (3 order types + 3 channels). Adding a new order type or channel requires only one additional subclass, turning multiplicative growth into additive growth.

Abstraction vs Implementor

In Bridge terminology, the “abstraction” is the high‑level dimension (e.g., OrderNotify) that defines *what* to do. The “implementor” is the low‑level capability (e.g., NotifyChannel) that defines *how* to do it. The abstraction holds a reference to the implementor, achieving decoupling and independent evolution.

Real‑world examples

JDBC: the java.sql package (Connection, Statement, DriverManager) is the abstraction; vendor‑specific drivers are the implementors. The application code holds a Driver via DriverManager, allowing the abstraction and implementation to evolve independently.

Logging frameworks: SLF4J provides a façade (abstraction) while Logback, Log4j2, etc., are implementors. The application code depends only on SLF4J; the underlying logger can be swapped without code changes.

Bridge vs related patterns

Strategy : both hold an interface reference, but Strategy swaps a single algorithm at runtime (one varying dimension). Bridge separates two dimensions that both evolve long‑term.

Adapter : Adapter retrofits incompatible interfaces after they exist. Bridge is a design‑time decision that deliberately separates orthogonal dimensions from the start.

When to use Bridge

Two or more orthogonal dimensions are expected to grow independently.

Using inheritance would cause a multiplicative class explosion.

Runtime flexibility to combine different implementations of each dimension is required.

When not to use Bridge

Only a single dimension varies (Strategy or other patterns may suffice).

One dimension is essentially stable; the benefit of separation is minimal.

Summary

Bridge converts multiplicative class growth into additive growth by decoupling orthogonal dimensions through composition. It exemplifies the broader principle “composition beats inheritance” in structural patterns.

Bridge pattern diagram
Bridge pattern diagram
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Design PatternsJavaObject-Oriented DesignStructural PatternsBridge PatternComposition over Inheritance
Dabaoshi
Written by

Dabaoshi

Practical utilities

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.