Bridge Pattern¶
Intent¶
The Bridge pattern decouples an abstraction from its implementation so that the two can vary independently. It separates the interface from the implementation and establishes a relationship between them through composition.
Also known as: Handle/Body
Problem¶
When an abstraction can have multiple implementations, inheritance is typically used to bind the implementation to the abstraction. However, this approach has several drawbacks:
- It permanently binds the implementation to the abstraction
- It makes it difficult to modify, extend, or reuse abstractions and implementations independently
- Changes to the implementation require recompiling the abstraction
- The class hierarchy can explode with combinations of abstractions and implementations
For example, if you have a Shape class that can be drawn on different platforms (Windows, Linux, macOS), and you want to support different types of shapes (Circle, Rectangle), using inheritance would result in classes like WindowsCircle, LinuxCircle, WindowsRectangle, LinuxRectangle, etc. This leads to a combinatorial explosion of classes.
Solution¶
The Bridge pattern suggests splitting a large class or a set of closely related classes into two separate hierarchies:
- Abstraction - The high-level control layer (interface/API)
- Implementation - The platform-specific code (low-level operations)
The abstraction contains a reference to an implementation object and delegates the actual work to it. This allows both hierarchies to evolve independently.
Structure¶
Participants¶
- Abstraction
- Defines the abstraction's interface
- Maintains a reference to an Implementation object
- In our example:
Abstractionclass withoperation()method
- RefinedAbstraction
- Extends the interface defined by Abstraction
- Can provide variants of the abstraction's operations
- In our example:
RefinedAbstractionclass
- Implementation
- Defines the interface for implementation classes
- Doesn't have to match Abstraction's interface exactly
- In our example:
Implementationinterface withoperationImplementation()
- ConcreteImplementation
- Implements the Implementation interface
- Provides platform-specific implementations
- In our example:
ConcreteImplementationAandConcreteImplementationB
- Client - Works with Abstraction objects - Doesn't know about implementation details
Implementation¶
Implementation Interface¶
Abstraction Class¶
Usage Example¶
Applicability¶
Use the Bridge pattern when:
- You want to avoid a permanent binding between abstraction and implementation - The implementation can be selected or switched at runtime - Example: Selecting database backend at runtime
- Both abstractions and implementations should be extensible by subclassing - Bridge lets you combine different abstractions with different implementations - Each can be extended independently
- Changes in the implementation should not impact clients - Implementation details are hidden from clients - You can change implementation without recompiling abstraction
- You want to share an implementation among multiple objects - Implementation can be shared (using reference counting) - Hidden from the client
- You have a proliferation of classes from a coupled interface and implementation - Class explosion from combinations (e.g., Windows/Linux × Circle/Rectangle) - Bridge reduces the number of classes
Consequences¶
Benefits¶
-
Decoupling interface and implementation - Implementation is not bound permanently to an interface - Can vary independently
-
Improved extensibility - Extend Abstraction and Implementation hierarchies independently - No impact on existing code
-
Hiding implementation details from clients - Client code depends only on Abstraction - Can change implementation without affecting clients
-
Reducing the number of classes - Avoids class explosion from combining multiple dimensions - Uses composition instead of inheritance
-
Runtime binding of implementation - Can change implementation at runtime - Swap implementations dynamically
Drawbacks¶
-
Increased complexity - Adds extra classes and indirection - May be overkill for simple scenarios
-
Performance overhead - Extra level of indirection through pointer - Usually negligible in practice
Bridge vs Adapter¶
While Bridge and Adapter have similar structures, they have different intents:
| Aspect | Bridge | Adapter |
|---|---|---|
| Intent | Separate abstraction from implementation to allow both to vary | Make incompatible interfaces work together |
| When | Designed up-front for flexibility | Applied to make existing classes work |
| Structure | Both hierarchies designed together | Wraps existing class |
| Purpose | Prevent class explosion, allow independent variation | Integrate legacy/third-party code |
| Participants | Abstraction and Implementation hierarchies | Target, Adapter, Adaptee |
Real-world Examples¶
-
GUI Frameworks - Abstraction: Window, Button, Dialog - Implementation: Windows API, X11, macOS Cocoa - Example: Qt, wxWidgets use Bridge for cross-platform support
-
Device Drivers - Abstraction: Printer, Scanner, Display - Implementation: HP Driver, Canon Driver, Epson Driver - Allows adding new devices and drivers independently
-
Database Access - Abstraction: Connection, Statement, ResultSet - Implementation: MySQL, PostgreSQL, Oracle drivers - Example: JDBC, ODBC use Bridge pattern
-
Graphics Rendering - Abstraction: Shape, Text, Image - Implementation: OpenGL, DirectX, Vulkan - Different rendering backends without changing shape code
-
Remote Communication - Abstraction: RemoteService, RemoteObject - Implementation: HTTP, TCP, WebSocket protocols - Switch protocols without changing service interface
Related Patterns¶
- Abstract Factory: Can be used to create and configure a particular Bridge
- Adapter: Adapter makes things work after they're designed; Bridge makes them work before they are designed
- State/Strategy: Similar structure but different intent. State/Strategy focus on behavior variation, Bridge focuses on structural variation
- Decorator: Has similar structure but different intent. Decorator adds responsibilities, Bridge separates interface from implementation
Implementation Considerations¶
-
One Implementation - Can start with one implementation and add more later - Bridge provides flexibility for future extensions
-
Creating the Implementation object - Abstraction can select the implementation - Can be passed in from outside (dependency injection) - Abstract Factory can help create the correct implementation
-
Sharing implementations - Multiple Abstractions can share the same Implementation - Use reference counting if needed
-
Multiple dimensions - Bridge works well when there are multiple dimensions of variation - Each dimension becomes a separate hierarchy
-
Implementation interface - Keep Implementation interface minimal - Define only primitive operations - Let Abstraction build higher-level operations from primitives
Sample Output¶
Key Takeaways¶
- Bridge decouples abstraction from implementation for independent variation
- Prevents class explosion from combining multiple dimensions
- Uses composition over inheritance
- Allows runtime selection of implementation
- Designed up-front, unlike Adapter which is applied later
- Ideal for cross-platform development and multiple implementation variants
- Both abstraction and implementation can be extended without affecting each other