Design Pattern (12) Bridge Pattern Complete Analysis: Decoupling Abstraction and Implementation, Building Flexible System Architecture

You can download the Design Pattern series code from this design_pattern repo.

In the previous Adapter Pattern article, we learned how to solve interface mismatch problems. Now let’s continue exploring another important structural pattern: Bridge Pattern.

Requirements

We received a complex enterprise-level requirement:

The company’s smart security system needs to send alerts to relevant personnel through multiple communication channels when different security events are detected. This system needs to have high flexibility and scalability.

Supported Notification Channels:

  • APNS (Apple iOS Push Notification)
  • FCM (Google Firebase Cloud Messaging)
  • Email (Electronic Mail)
  • SMS (Short Message Service)

Alert Event Types:

  • Fire (Fire Alarm)
  • Burglar (Intrusion Alarm)

System Requirements:

Each alert type should be able to be sent through any notification channel, and new alert types or notification methods may be added in the future.

Object-Oriented Analysis (OOA)

Before starting the design, let’s first conduct object-oriented analysis to understand the core components in the system and their relationships:

Identifying Forces

When facing this type of multi-dimensional design problem, without using appropriate design patterns, we face the following challenges:

1. Combinatorial Explosion Problem

When we have 2 alert types and 4 notification methods, if we create a class for each combination:

  • We need 2 × 4 = 8 concrete classes
  • If we add one alert type, we need to add 4 more classes
  • If we add one notification method, we need to add 2 more classes

2. Tight Coupling Problem

  • Alert types and notification methods are forcibly bound together
  • Modifying one dimension may affect multiple classes
  • Dependencies between system parts are too tight

3. Poor Extensibility

  • Adding alert types: Need to create corresponding classes for each notification method
  • Adding notification methods: Need to create corresponding classes for each alert type
  • Each extension may lead to large-scale code modifications

4. Code Duplication Problem

  • Similar alert handling logic appears repeatedly in multiple classes
  • Same notification method implementations are copied to multiple places
  • When core logic needs modification, must update synchronously in multiple places

5. High Maintenance Cost

  • Any change in one dimension may affect multiple classes
  • Difficult to predict the impact scope of modifications
  • System complexity grows exponentially with the number of combinations

Applying Bridge Pattern (Solution) to Achieve New Context (Resulting Context)

After completing OOA, identifying Forces, and understanding the entire Context, we can apply the Bridge Pattern to solve this problem.

Let’s first look at the UML of the Bridge Pattern:

  • Abstraction: Defines notification functionality, responsible for using specific message sending methods to send notifications.
  • RefinedAbstraction: Extends the abstraction layer, implementing different types of alert notifications, such as fire alarm notifications or burglar alarm notifications.
  • Implementor: Defines the interface for message sending, responsible for handling specific message sending logic.
  • ConcreteImplementor: Provides specific message sending implementations, such as APNS, FCM, Email, SMS.

Let’s apply the Bridge Pattern to our application:

Object-Oriented Programming (OOP)

[Abstraction: AlarmNotification]

abstract class AlarmNotification(sender: MessageSender) {
    protected var sender: MessageSender

    init {
        this.sender = sender
    }

    abstract fun notifyUser(details: String?)
}

[RefinedAbstraction: FireAlarmNotification and BurglarAlarmNotification]

class FireAlarmNotification(sender: MessageSender) : AlarmNotification(sender) {
    override fun notifyUser(details: String?) {
        sender.sendMessage("Fire Alarm: $details")
    }
}

class BurglarAlarmNotification(sender: MessageSender) : AlarmNotification(sender) {
    override fun notifyUser(details: String?) {
        sender.sendMessage("Theft Alarm: $details")
    }
}

[Implementor: MessageSender]

interface MessageSender {
    fun sendMessage(message: String?)
}

[ConcreteImplementor: APNSSender, FCMSender, EmailSender, and SMSSender]

class APNSSender : MessageSender {
    override fun sendMessage(message: String?) {
        println("Sending APNS Notification: $message")
    }
}

class FCMSender : MessageSender {
    override fun sendMessage(message: String?) {
        println("Sending FCM Notification: $message")
    }
}

class EmailSender : MessageSender {
    override fun sendMessage(message: String?) {
        println("Sending Email: $message")
    }
}

class SMSSender : MessageSender {
    override fun sendMessage(message: String?) {
        println("Sending SMS: $message")
    }
}

[Client]

fun main() {
    // Sending Fire Alarm via APNS
    val fireAPNS: AlarmNotification = FireAlarmNotification(APNSSender())
    fireAPNS.notifyUser("Smoke detected in Zone 1.")

    // Sending Burglar Alarm via FCM
    val burglarFCM: AlarmNotification = BurglarAlarmNotification(FCMSender())
    burglarFCM.notifyUser("Unauthorized access detected at Main Door.")

    // Sending Fire Alarm via Email
    val fireEmail: AlarmNotification = FireAlarmNotification(EmailSender())
    fireEmail.notifyUser("Temperature exceeds threshold in Zone 3.")

    // Sending Burglar Alarm via SMS
    val burglarSMS: AlarmNotification = BurglarAlarmNotification(SMSSender())
    burglarSMS.notifyUser("Motion detected in Warehouse.")
}

Execution Results and Analysis

When we execute the above code, we get the following output:

Sending APNS Notification: Fire Alarm: Smoke detected in Zone 1.
Sending FCM Notification: Theft Alarm: Unauthorized access detected at Main Door.
Sending Email: Fire Alarm: Temperature exceeds threshold in Zone 3.
Sending SMS: Theft Alarm: Motion detected in Warehouse.

This result shows that different alert types can be combined with different notification methods, and the combination methods are very flexible.

Conclusion

By applying the Bridge Pattern, we successfully solved the multi-dimensional design challenge:

Benefits Achieved:

1. Separation of Concerns

  • Alert types and notification methods are independent of each other and can evolve separately
  • Modifying one dimension does not affect the other dimension

2. Elegant Extensibility

  • Adding alert types: Only need to create a new RefinedAbstraction
  • Adding notification methods: Only need to create a new ConcreteImplementor
  • Avoids the combinatorial explosion problem

3. Improved Code Reusability

  • Same notification methods can be reused by different alert types
  • Same alert logic can work with different notification methods

4. Reduced System Complexity

  • Number of classes reduced from O(m×n) to O(m+n)
  • System understandability and maintainability greatly improved

Applicable Scenarios:

Bridge Pattern is particularly suitable for:

  • Systems that need to provide flexibility between abstraction and implementation
  • Design problems with multiple combination dimensions
  • Situations that need to dynamically switch implementation methods at runtime

Bridge Pattern, together with Adapter Pattern, forms two important foundations in structural patterns, laying a solid foundation for us to learn other more complex structural patterns later.




    Enjoy Reading This Article?

    Here are some more articles you might like to read next:

  • Claude Code 使用技巧與最佳實踐 - Tips and Best Practices
  • 🤖 AI Agent Series (Part 1): Understanding the Core Interaction Logic of LLM, RAG, and MCP
  • 💡 Managing Multiple GitHub Accounts on One Computer: The Simplest SSH Configuration Method
  • 🚀 How to Use Excalidraw AI to Quickly Generate Professional Diagrams and Boost Work Efficiency!
  • Complete macOS Development Environment Setup Guide: Mobile Development Toolchain Configuration Tutorial