Skip to main content
Create your own
Lesson illustration

Decoupling with the Bridge Pattern

Hello! Welcome to our final lesson in the Structural Design Patterns module.

In our last session, we explored the Composite pattern, which allows us to build tree-like object structures and treat individual objects and groups of objects uniformly.

Today, we will tackle the Bridge pattern. Your learning outcome is to apply the Bridge pattern to decouple an abstraction from its implementation. This is a powerful technique for managing complexity, especially in systems that need to support multiple platforms or variations of a core feature. It's a common pattern to leverage in system design interviews to demonstrate your ability to create flexible and extensible systems.

The Problem: A Combinatorial Class Explosion

Imagine you're designing a system that can play different types of videos (e.g., YouTube videos, Netflix videos) at various quality levels (e.g., HD, 4K, 8K).

If you were to use inheritance to model this, you might start with a Video class and create subclasses like YouTubeVideo and NetflixVideo. But how do you handle the quality? You'd have to create even more subclasses:

  • YouTubeHDVideo
  • YouTube4KVideo
  • NetflixHDVideo
  • Netflix4KVideo

If a new platform like PrimeVideo is introduced, you have to add PrimeHDVideo, Prime4KVideo, etc. If a new quality like 8K is added, you have to create an 8K version for every platform. This is called a combinatorial explosion of classes. With N video platforms and M quality processors, you end up with N * M classes. This design is rigid and difficult to maintain.

The video below explains this exact problem and sets the stage for how the Bridge pattern provides a solution.

Bridge Design Pattern in detail | Interview Question (Structural Design Pattern)

This video from Daily Code Buffer clearly illustrates the problem of class explosion using the video streaming example. It will help you visualize why a simple inheritance-based approach fails here.

Watch from the beginning to 01:55. Pay close attention to the diagram showing how the number of classes grows exponentially as new video types and quality options are added.

The Solution: Decoupling with a "Bridge"

The Bridge pattern solves this problem by separating the two dimensions of variation into two independent class hierarchies. Instead of one giant inheritance tree, you create two smaller ones:

  1. The Abstraction: The high-level concept the client interacts with (e.g., a Video).
  2. The Implementation: The underlying platform-specific or variant-specific logic (e.g., a VideoProcessor for HD or 4K).

The "bridge" is a composition relationship: the Abstraction has an Implementation. This changes the relationship from a rigid "is-a" (inheritance) to a flexible "has-a" (composition), aligning with the "composition over inheritance" principle we discussed in Module 2.

This approach reduces the number of classes from N * M to N + M, which is far more manageable.

Structure of the Bridge Pattern

To formalize this, the Bridge pattern has four key components.

Bridge Method Design Pattern in Java

Let's learn the formal terminology for the parts of the Bridge pattern from this GeeksforGeeks article. Understanding these terms is useful for clearly communicating your design.

Read the section 'Components of Bridge Method Design Pattern in Java'. As you read, try to map these four terms to our video streaming example:

Here's a summary of those components:

  1. Abstraction: Defines the high-level interface for the client (e.g., an abstract Video class with a play() method). It holds a reference to an Implementor.
  2. RefinedAbstraction: Extends the Abstraction to provide specific variations (e.g., YouTubeVideo, NetflixVideo).
  3. Implementor: An interface that defines the operations for the underlying implementation (e.g., a VideoProcessor interface with a process() method).
  4. ConcreteImplementor: Concrete classes that implement the Implementor interface (e.g., HDProcessor, 4KProcessor).

This UML diagram provides a classic visual representation using Shapes and Colors. The Shape is the abstraction, and Color is the implementation. They are connected by a bridge.

UML Class Diagram for Bridge Design Pattern (Shape and Color Example)
This UML diagram shows the Bridge pattern structure. The `Shape` hierarchy (Abstraction) is decoupled from the `Color` hierarchy (Implementation). The `Shape` class contains a reference to a `Color` object, forming the 'bridge'.

Java Implementation: A Notification Service

Let's walk through a complete Java implementation. Designing a notification service is a common LLD interview question, and it's a perfect use case for the Bridge pattern. We need to send different types of notifications (Text, QR Code) through various channels (SMS, Email, WhatsApp).

Here, the two dimensions are:

  • Abstraction: The message type (Notification).
  • Implementation: The sending mechanism (NotificationSender).

The following video provides an excellent code walkthrough.

Bridge Design Pattern | System Design Notification Service | Object Oriented Design Patterns

This video from The Tech Granth demonstrates how to build a notification service using the Bridge pattern. Pay close attention to how the Abstraction and Implementor hierarchies are created and linked.

Watch from 04:03 to 16:50. This covers the 'why' and the 'how'. (04:03 - 08:49): The video explains why Bridge is a good fit and explicitly discusses the N*M vs. N+M benefit. It identifies the Abstraction (message type) and Implementation (sending mechanism). (08:49 - 16:50): Follow the Java implementation closely. Notice how NotificationSender is the Implementor interface and Sms and Email are ConcreteImplementors. See how the Notification abstract class is the Abstraction, holding a NotificationSender reference. Finally, observe how TextMessage and QrMessage are the RefinedAbstractions, which use the NotificationSender to do their work.

To see how the parts interact at runtime, consider this sequence diagram. When the client calls an operation on the Abstraction (e.g., textMessage.send()), the Abstraction in turn calls the corresponding method on its contained Implementor object (e.g., emailSender.sendNotification()).

Bridge Pattern Sequence Diagram
This sequence diagram shows the flow of control. The client calls a method on the Abstraction, which delegates the actual work to the Implementor object it holds.
Test your understanding!

You are asked to design a drawing application. The application should be able to draw different shapes (like Circle and Square) on various operating systems (like Windows and MacOS). The underlying drawing APIs are different for each OS. For instance, draw_line_win() on Windows vs. draw_line_mac() on MacOS.

How would you use the Bridge pattern to structure this design? Identify the four main components (Abstraction, RefinedAbstraction, Implementor, ConcreteImplementor).

Show answer

This is a classic use case for the Bridge pattern because you have two independent dimensions: the shape being drawn and the OS it's being drawn on.

  • Implementor: An interface, let's call it DrawingAPI, that declares common low-level drawing operations.
    interface DrawingAPI {
        void drawCircle(double x, double y, double radius);
    }
    
  • ConcreteImplementor: Classes that provide OS-specific implementations of the DrawingAPI.
    class WindowsAPI implements DrawingAPI { /* ... implementation using Windows calls ... */ }
    class MacOSAPI implements DrawingAPI { /* ... implementation using MacOS calls ... */ }
    
  • Abstraction: An abstract Shape class that holds a reference to a DrawingAPI object. This is the bridge.
    abstract class Shape {
        protected DrawingAPI drawingAPI;
        protected Shape(DrawingAPI drawingAPI) { this.drawingAPI = drawingAPI; }
        public abstract void draw();
    }
    
  • RefinedAbstraction: Concrete shape classes that extend Shape. They implement the draw() method by making high-level calls, which are then delegated to the DrawingAPI implementor.
    class Circle extends Shape {
        private double x, y, radius;
        public Circle(double x, double y, double radius, DrawingAPI drawingAPI) {
            super(drawingAPI);
            this.x = x; this.y = y; this.radius = radius;
        }
        public void draw() {
            drawingAPI.drawCircle(x, y, radius);
        }
    }
    

With this structure, you can create a Circle with a WindowsAPI or a MacOSAPI at runtime, completely decoupling the concept of a Circle from how it is actually rendered on screen.

When to Use the Bridge Pattern

Justifying your choice of a design pattern is critical in an interview. Here are the key scenarios where the Bridge pattern shines.

Bridge Method Design Pattern in Java

The article 'Bridge Method Design Pattern in Java' clearly lists when you should and should not use this pattern. Reviewing these points will help you articulate your design decisions.

Read the sections 'When to Use Bridge Method Design Pattern in Java' and 'When Not to Use Bridge Method Design Pattern in Java'.

In summary, use the Bridge pattern when:

  • You want to avoid a permanent binding between an abstraction and its implementation.
  • You have two orthogonal (independent) dimensions of variation in your classes.
  • You need to be able to switch or select implementations at runtime.
  • You want to hide implementation details from clients, which only need to know about the abstraction.

Avoid it when your system is simple and the abstraction and implementation are not expected to change independently, as it can add unnecessary complexity.

Conclusion

Congratulations on completing the Structural Design Patterns module! You've learned how to apply the Bridge pattern to create flexible and extensible systems by decoupling an abstraction from its implementation.

Key Takeaways:

  • Purpose: To decouple an abstraction from its implementation so that the two can vary independently.
  • Core Problem Solved: It prevents a "combinatorial explosion" of classes when you have multiple, independent dimensions of variation (N*M classes become N+M).
  • Mechanism: It replaces inheritance with composition. The Abstraction has an Implementor and delegates work to it.
  • Structure: It involves two separate class hierarchies: one for the Abstraction and one for the Implementor.
  • Interview Cue: Reach for this pattern when a problem statement implies variations along two axes, like "different types of X on multiple platforms Y" or "different versions of X using various mechanisms Y".

This lesson concludes our exploration of structural patterns. In our next module, we will dive into Behavioral Design Patterns, starting with the Observer pattern. These patterns are concerned with algorithms and the assignment of responsibilities between objects, focusing on how they communicate and interact.

Can't find a good explanation? Sign up and we'll make it for you

Sign up