Bird
Raised Fist0
LLDsystem_design~7 mins

Bridge pattern in LLD - System Design Guide

Choose your learning style10 modes available
Problem Statement
When a system has multiple dimensions of variation, combining them directly leads to a large number of classes that are hard to maintain and extend. For example, if you have different types of devices and different ways to control them, creating a class for every combination causes code duplication and rigidity.
Solution
The Bridge pattern separates the abstraction (interface) from its implementation so they can vary independently. It uses composition to link an abstraction object to an implementation object, allowing you to change either side without affecting the other. This reduces the number of classes and improves flexibility.
Architecture
Abstraction
(interface)
→Implementation
↓
RefinedAbst.
↓
ConcreteImplB
ConcreteImplB

This diagram shows the abstraction and implementation as separate hierarchies connected by a bridge. The abstraction delegates work to the implementation, allowing both to evolve independently.

Trade-offs
✓ Pros
→
Reduces the number of classes by decoupling abstraction and implementation.
→
Improves code flexibility and extensibility by allowing independent changes.
→
Supports runtime switching of implementations without changing the abstraction.
✗ Cons
→
Introduces additional layers of indirection, which can add complexity.
→
Requires careful design to keep abstraction and implementation interfaces stable.
→
May increase the number of objects created, impacting memory usage.
Use when a system has multiple independent dimensions of variation that would otherwise cause combinatorial explosion of classes, especially when you expect these dimensions to change or extend over time.
Avoid when the abstraction and implementation are unlikely to change independently or when the system is simple with few variations, as the added complexity is unnecessary.
Real World Examples
Microsoft
Used in GUI frameworks to separate window abstractions from platform-specific implementations, allowing the same window interface to work on different operating systems.
IBM
Applied in printer drivers to separate the printer interface from the actual printer hardware implementations, enabling support for multiple printer models.
Google
Used in Android to separate UI components from rendering engines, allowing different rendering backends without changing UI logic.
Code Example
The before code tightly couples dialog classes with specific button implementations, causing class explosion. The after code separates the abstraction (Dialog) from the implementation (ButtonImplementation), allowing independent extension and reuse.
LLD
### Before Bridge Pattern (tight coupling, many classes)

class WindowsButton:
    def draw(self):
        print("Drawing button in Windows style")

class MacOSButton:
    def draw(self):
        print("Drawing button in MacOS style")

class WindowsDialog:
    def render(self):
        button = WindowsButton()
        button.draw()

class MacOSDialog:
    def render(self):
        button = MacOSButton()
        button.draw()

### After Bridge Pattern (decoupled abstraction and implementation)

class ButtonImplementation:
    def draw_button(self):
        pass

class WindowsButtonImpl(ButtonImplementation):
    def draw_button(self):
        print("Drawing button in Windows style")

class MacOSButtonImpl(ButtonImplementation):
    def draw_button(self):
        print("Drawing button in MacOS style")

class Dialog:
    def __init__(self, button_impl: ButtonImplementation):
        self.button_impl = button_impl

    def render(self):
        self.button_impl.draw_button()

# Usage
windows_dialog = Dialog(WindowsButtonImpl())
windows_dialog.render()  # Drawing button in Windows style

mac_dialog = Dialog(MacOSButtonImpl())
mac_dialog.render()      # Drawing button in MacOS style
OutputSuccess
Alternatives
Adapter pattern
Adapter changes the interface of an existing class to match what a client expects, usually for legacy or third-party code integration.
Use when: Choose Adapter when you need to make incompatible interfaces work together without changing their code.
Decorator pattern
Decorator adds responsibilities to objects dynamically without changing their interface, focusing on behavior extension rather than abstraction separation.
Use when: Choose Decorator when you want to add features to objects at runtime without affecting other objects.
Summary
Bridge pattern separates abstraction from implementation to reduce class explosion and improve flexibility.
It uses composition to connect abstraction and implementation hierarchies that can evolve independently.
This pattern is useful when multiple independent variations exist and need to be combined cleanly.

Practice

(1/5)
1. What is the main purpose of the Bridge pattern in system design?
easy
A. To create a single class that handles all functionalities
B. To separate an abstraction from its implementation so they can vary independently
C. To enforce a strict inheritance hierarchy without flexibility
D. To combine multiple unrelated classes into one

Solution

  1. Step 1: Understand the Bridge pattern concept

    The Bridge pattern is designed to separate an abstraction from its implementation, allowing both to change independently.
  2. Step 2: Compare options with the concept

    To separate an abstraction from its implementation so they can vary independently correctly states this separation. Other options describe different or incorrect design approaches.
  3. Final Answer:

    To separate an abstraction from its implementation so they can vary independently -> Option B
  4. Quick Check:

    Bridge pattern = separation of abstraction and implementation [OK]
Hint: Bridge = separate interface and implementation [OK]
Common Mistakes:
  • Confusing Bridge with Adapter or Decorator patterns
  • Thinking Bridge merges classes instead of separating
  • Assuming Bridge enforces rigid inheritance
2. Which of the following is the correct way to define the Bridge pattern structure in code?
easy
A. class Implementor extends Abstraction {}
B. class Abstraction extends Implementor {}
C. class Abstraction { Implementor impl; }
D. class Abstraction implements Implementor {}

Solution

  1. Step 1: Recall Bridge pattern structure

    Bridge pattern uses composition: the abstraction holds a reference to the implementor interface.
  2. Step 2: Analyze each option

    class Abstraction { Implementor impl; } correctly shows the required composition. The other options use inheritance (extends) or interface implementation (implements), which defeats the purpose of decoupling via composition.
  3. Final Answer:

    class Abstraction { Implementor impl; } -> Option C
  4. Quick Check:

    Bridge uses composition, not inheritance [OK]
Hint: Bridge uses composition, not inheritance [OK]
Common Mistakes:
  • Using inheritance instead of composition
  • Confusing implements keyword with composition
  • Ignoring the implementor interface role
3. Given the following code snippet using the Bridge pattern, what will be the output?
class DrawingAPI {
  drawCircle(x, y, radius) { console.log(`API drawing circle at (${x},${y}) with radius ${radius}`); }
}

class Shape {
  constructor(drawingAPI) { this.drawingAPI = drawingAPI; }
  draw() {}
}

class Circle extends Shape {
  constructor(x, y, radius, drawingAPI) {
    super(drawingAPI);
    this.x = x; this.y = y; this.radius = radius;
  }
  draw() {
    this.drawingAPI.drawCircle(this.x, this.y, this.radius);
  }
}

const api = new DrawingAPI();
const circle = new Circle(5, 10, 7, api);
circle.draw();
medium
A. API drawing circle at (5,10) with radius 7
B. Circle drawn at (5,10) with radius 7
C. Error: drawCircle is not a function
D. No output

Solution

  1. Step 1: Trace the draw() method call

    The Circle's draw method calls drawingAPI.drawCircle with x=5, y=10, radius=7.
  2. Step 2: Check DrawingAPI's drawCircle implementation

    It logs: "API drawing circle at (5,10) with radius 7" to the console.
  3. Final Answer:

    API drawing circle at (5,10) with radius 7 -> Option A
  4. Quick Check:

    drawCircle logs correct message [OK]
Hint: Follow method calls and parameters carefully [OK]
Common Mistakes:
  • Assuming drawCircle is undefined
  • Confusing output message text
  • Ignoring parameter values passed
4. Identify the error in the following Bridge pattern implementation:
class Implementor {
  operation() { console.log('Implementor operation'); }
}

class Abstraction {
  constructor() { this.impl = new Implementor(); }
  operation() { this.impl.operation(); }
}

const abs = new Abstraction();
abs.operation();
medium
A. Abstraction should not create Implementor instance directly
B. Implementor should be an interface or abstract class, not a concrete class
C. operation method should return a value
D. No error, code works fine

Solution

  1. Step 1: Review Bridge pattern best practice

    Abstraction should receive Implementor via constructor injection, not create it internally, to allow flexibility.
  2. Step 2: Analyze code's constructor

    Abstraction creates Implementor directly, which tightly couples them and breaks Bridge pattern intent.
  3. Final Answer:

    Abstraction should not create Implementor instance directly -> Option A
  4. Quick Check:

    Bridge needs abstraction to hold implementor, not create it [OK]
Hint: Inject implementor, don't instantiate inside abstraction [OK]
Common Mistakes:
  • Ignoring tight coupling caused by direct instantiation
  • Thinking concrete class Implementor is always wrong
  • Expecting operation to return value unnecessarily
5. You are designing a graphics system that supports multiple shapes (Circle, Square) and multiple rendering APIs (OpenGL, DirectX). How does the Bridge pattern help in this design?
hard
A. By using inheritance to create a subclass for each shape-API combination
B. By forcing all shapes to use a single rendering API implementation
C. By merging shape and rendering API classes into one large class
D. By allowing shapes and rendering APIs to vary independently without subclass explosion

Solution

  1. Step 1: Understand the problem of subclass explosion

    If each shape-API pair requires a subclass, the number grows quickly (e.g., CircleOpenGL, CircleDirectX).
  2. Step 2: Apply Bridge pattern advantage

    Bridge separates shape abstraction from rendering implementation, so shapes and APIs can be combined dynamically without many subclasses.
  3. Final Answer:

    By allowing shapes and rendering APIs to vary independently without subclass explosion -> Option D
  4. Quick Check:

    Bridge reduces subclass explosion by separating abstraction and implementation [OK]
Hint: Bridge avoids subclass explosion by separating concerns [OK]
Common Mistakes:
  • Thinking Bridge merges classes instead of separating
  • Assuming inheritance must handle all combinations
  • Ignoring dynamic combination of abstraction and implementation
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard