Skip to content

Bridge Pattern: Two Things That Change, Kept Apart

Split a class that varies in two directions into two hierarchies joined by a reference, so 3 x 3 combinations need 6 classes instead of 9.

Builds on Composition vs Inheritance

Stage 3advanced2 min read

Most of us write it this way the first time. There is a base Notification. Each pair of "what we say" and "how we send it" gets its own subclass.

The setup

Swiggy style notifications: three message types and three channels.

SMSEmailWhatsApp
OTPOtpSmsNotificationOtpEmailNotificationOtpWhatsAppNotification
PromoPromoSmsNotificationPromoEmailNotificationPromoWhatsAppNotification
Order updateOrderUpdateSmsNotificationOrderUpdateEmailNotificationOrderUpdateWhatsAppNotification

Nine classes. Each one looks like this:

class OtpSmsNotification extends Notification {

  @Override
  void send(String to) {
    String text = "Your Swiggy OTP is 482913. Valid for 5 minutes.";
    System.out.println("[SMS via gateway] to " + to + ": " + text);
  }
}

One class looks harmless. The trouble shows when you put all nine side by side.

What goes wrong

1. The count multiplies

Classes = message types x channels. Today that is 3 x 3 = 9.

  • Add Push: 3 x 4 = 12.
  • Add a "Rate your order" message too: 4 x 4 = 16.

Every new item on either axis adds a full new row or column of classes to the table above.

2. The same text lives in three places

// OtpSmsNotification
String text = "Your Swiggy OTP is 482913. Valid for 5 minutes.";
// OtpEmailNotification
String text = "Your Swiggy OTP is 482913. Valid for 5 minutes.";
// OtpWhatsAppNotification
String text = "Your Swiggy OTP is 482913. Valid for 5 minutes.";

Security asks you to change the OTP expiry to 10 minutes. You update two copies and miss the third. Now WhatsApp users see the wrong expiry.

3. The same sending code lives in three places

Every *Sms* class has its own copy of the SMS call. In a real system that copy holds the gateway URL, the API key, the DLT template id (an Indian SMS rule: each message text must be registered) and the retry logic. Switch SMS providers and you edit three files. With 5 message types it is five files.

4. The channel is baked into the type

new OtpSmsNotification().send("+91 98450 12345");

Say the SMS provider goes down at 8 pm on a Friday (peak dinner time) and you want OTPs to fall back to WhatsApp. You cannot pass a different channel. You must build a different class, so every place that sends an OTP needs an if.

5. Two reasons to change, one class

OtpSmsNotification changes when the OTP copy changes and also when the SMS integration changes. Two teams (growth and platform) end up editing the same file.

What the run prints

[SMS via gateway] to +91 98450 12345: Your Swiggy OTP is 482913. Valid for 5 minutes.
[Email via SMTP] to priya@example.com: Flat 50% off up to Rs 100 on your next order. Code: TRYNEW
[WhatsApp via Business API] to +91 98450 12345: Order #SW1043 from Meghana Foods is out for delivery.
[WhatsApp via Business API] to +91 98450 12345: Your Swiggy OTP is 482913. Valid for 5 minutes.

The output is fine. The code behind it is what hurts.

The smell in one line

If your class names look like <Thing><Way> and make a grid, two dimensions are squeezed into one hierarchy. That is the signal for Bridge.

Run this version

From the repository root, create the output directory, compile this example, and run its fully qualified class name:

mkdir -p out
javac -d out src/DesignPatterns/StructuralPatterns/BridgeDesignPattern/Before/Main.java
java -cp out DesignPatterns.StructuralPatterns.BridgeDesignPattern.Before.Main

All classes sit in one Main.java so the example is easy to run. The problem is the same as in a multi-file application. Each combination is a separate class, and each class mixes one message choice with one channel choice.

In this topic

Before code1 file

Before/Main.java112 linesGitHub
package DesignPatterns.StructuralPatterns.BridgeDesignPattern.Before;/** * PROBLEM: every message type is glued to one channel through inheritance. * 3 message types x 3 channels = 9 classes. Add Push and it becomes 12. */abstract class Notification {  abstract void send(String to);}// ----- OTP on every channel -----class OtpSmsNotification extends Notification {  @Override  void send(String to) {    String text = "Your Swiggy OTP is 482913. Valid for 5 minutes.";    System.out.println("[SMS via gateway] to " + to + ": " + text);  }}class OtpEmailNotification extends Notification {  @Override  void send(String to) {    String text = "Your Swiggy OTP is 482913. Valid for 5 minutes.";    System.out.println("[Email via SMTP] to " + to + ": " + text);  }}class OtpWhatsAppNotification extends Notification {  @Override  void send(String to) {    String text = "Your Swiggy OTP is 482913. Valid for 5 minutes.";    System.out.println("[WhatsApp via Business API] to " + to + ": " + text);  }}// ----- Promo on every channel -----class PromoSmsNotification extends Notification {  @Override  void send(String to) {    String text = "Flat 50% off up to Rs 100 on your next order. Code: TRYNEW";    System.out.println("[SMS via gateway] to " + to + ": " + text);  }}class PromoEmailNotification extends Notification {  @Override  void send(String to) {    String text = "Flat 50% off up to Rs 100 on your next order. Code: TRYNEW";    System.out.println("[Email via SMTP] to " + to + ": " + text);  }}class PromoWhatsAppNotification extends Notification {  @Override  void send(String to) {    String text = "Flat 50% off up to Rs 100 on your next order. Code: TRYNEW";    System.out.println("[WhatsApp via Business API] to " + to + ": " + text);  }}// ----- Order update on every channel -----class OrderUpdateSmsNotification extends Notification {  @Override  void send(String to) {    String text = "Order #SW1043 from Meghana Foods is out for delivery.";    System.out.println("[SMS via gateway] to " + to + ": " + text);  }}class OrderUpdateEmailNotification extends Notification {  @Override  void send(String to) {    String text = "Order #SW1043 from Meghana Foods is out for delivery.";    System.out.println("[Email via SMTP] to " + to + ": " + text);  }}class OrderUpdateWhatsAppNotification extends Notification {  @Override  void send(String to) {    String text = "Order #SW1043 from Meghana Foods is out for delivery.";    System.out.println("[WhatsApp via Business API] to " + to + ": " + text);  }}public class Main {  public static void main(String[] args) {    // The caller has to pick the exact combination class up front.    new OtpSmsNotification().send("+91 98450 12345");    new PromoEmailNotification().send("priya@example.com");    new OrderUpdateWhatsAppNotification().send("+91 98450 12345");    // Changing the channel means changing the class, not passing a value.    // Switching OTP from SMS to WhatsApp? Find and replace OtpSmsNotification everywhere.    new OtpWhatsAppNotification().send("+91 98450 12345");  }}

Done with this topic?

How well do you know it?

PreviousMemory Optimization through Sharing
Practise 7 questions