Decouple an abstraction from its implementation so that the two can vary independently — avoiding a permanent binding between them.
The Bridge pattern separates a class into two independent hierarchies — the abstraction (what it does) and the implementation (how it does it) — connected by a reference (the bridge).
Imagine a notification system: you have notification types (Alert, Reminder) and delivery channels (Email, SMS). Without Bridge, you'd need AlertEmail, AlertSMS, etc. With Bridge, you just compose them at runtime.
Key Idea: Use composition instead of inheritance to connect hierarchies. The abstraction delegates platform-specific work to an implementor.
Without Bridge, adding dimensions to a class hierarchy causes class count to multiply (M × N):
// Without Bridge: 3 types × 3 channels = 9 classes
class AlertEmailNotification {}
class AlertSMSNotification {}
...
// With Bridge: 3 types + 3 senders = 6 classes
class AlertNotification { constructor(sender) {} }
class EmailSender {}
// new AlertNotification(new EmailSender()) — dynamic combo!The high-level control layer. Holds a reference to an Implementor (the bridge).
Extends Abstraction with specific business logic while still delegating to the implementor.
Defines the low-level operations interface for the implementation layer.
The actual "how"—the platform-specific code (e.g., SMTP for email, Twilio for SMS).
// Bridge Pattern — Notification System
// ─── Implementor interface ────────────────────────────────────
interface MessageSender {
void send(String recipient, String subject, String body);
}
// ─── Concrete Implementors ────────────────────────────────────
class EmailSender implements MessageSender {
public void send(String recipient, String subject, String body) {
System.out.println("[Email] To: " + recipient + " | Subject: " + subject + " | " + body);
}
}
class SMSSender implements MessageSender {
public void send(String recipient, String subject, String body) {
// SMS has no subject, truncate body
System.out.println("[SMS] To: " + recipient + " | " + body.substring(0, Math.min(160, body.length())));
}
}
class SlackSender implements MessageSender {
public void send(String recipient, String subject, String body) {
System.out.println("[Slack] Channel: #" + recipient + " | *" + subject + "* " + body);
}
}
// ─── Abstraction ──────────────────────────────────────────────
abstract class Notification {
protected MessageSender sender; // the bridge
public Notification(MessageSender sender) { this.sender = sender; }
public abstract void notify(String recipient, String message);
}
// ─── Refined Abstractions ─────────────────────────────────────
class AlertNotification extends Notification {
public AlertNotification(MessageSender sender) { super(sender); }
public void notify(String recipient, String message) {
sender.send(recipient, "🚨 ALERT", "[URGENT] " + message);
}
}
class ReminderNotification extends Notification {
public ReminderNotification(MessageSender sender) { super(sender); }
public void notify(String recipient, String message) {
sender.send(recipient, "⏰ Reminder", message);
}
}
// ─── Client ───────────────────────────────────────────────────
public class App {
public static void main(String[] args) {
// Mix and match abstraction × implementation freely
Notification emailAlert = new AlertNotification(new EmailSender());
Notification smsAlert = new AlertNotification(new SMSSender());
Notification slackRemind = new ReminderNotification(new SlackSender());
emailAlert.notify("admin@example.com", "Server CPU at 98%!");
smsAlert.notify("+91-9876543210", "Server CPU at 98%!");
slackRemind.notify("devops", "Deployment at 3pm today");
// 3 types × 3 senders = 9 combos — zero new classes needed!
}
}When a class hierarchy grows in two directions at once (e.g., FileTypes × StorageBackends).
When you need to swap the engine without changing the interface (e.g., Payment Gateway A to B).
To keep client code purely business-focused, leaving technical specifics to the implementors.