Author: David Martinez
Created: December 3, 2024 - Updated: December 4, 2024
Read Time: 15 min
Hello there! Today, letโs unravel the mysteries of the Bridge design pattern. ๐ We'll dive into its purpose, practical applications, and bring it to life with a Ruby implementation.
The Bridge is a structural design pattern aimed at decoupling an abstraction from its implementation.
This pattern is especially useful when you need to combine different dimensions of variability or want to avoid a complex inheritance hierarchy.
๐น Use the Bridge pattern when your code needs to work with multiple types of implementations without being tightly coupled to them.
๐น Itโs ideal for scenarios where you anticipate future expansions of both abstractions and their implementations.
๐น Great for scenarios involving cross-platform development, where the same code must work with different systems.
Imagine a scenario where we need to render various shapes (e.g., circles, rectangles) using different rendering APIs like OpenGL and DirectX.
A naive implementation would require creating separate classes for each combination of shape and rendering API, leading to a combinatorial explosion of classes as more shapes or APIs are introduced.
In this example, we have separate classes for each shape and rendering API.
This approach leads to a large number of classes and tightly couples shapes with rendering APIs.

In this example:
๐น OpenGLCircle, OpenGLRectangle, DirectXCircle, DirectXRectangle are concrete classes for rendering shapes using specific APIs.
๐น main creates instances of each class and calls the draw method to render the shapes.
class OpenGLCircledef initialize(x, y, radius)@x, @y, @radius = x, y, radiusenddef drawputs "Rendering circle at (#{@x}, #{@y}) with radius #{@radius} using OpenGL"endendclass OpenGLRectangledef initialize(x, y, width, height)@x, @y, @width, @height = x, y, width, heightenddef drawputs "Rendering rectangle at (#{@x}, #{@y}) with width #{@width} and height #{@height} using OpenGL"endendclass DirectXCircledef initialize(x, y, radius)@x, @y, @radius = x, y, radiusenddef drawputs "Rendering circle at (#{@x}, #{@y}) with radius #{@radius} using DirectX"endendclass DirectXRectangledef initialize(x, y, width, height)@x, @y, @width, @height = x, y, width, heightenddef drawputs "Rendering rectangle at (#{@x}, #{@y}) with width #{@width} and height #{@height} using DirectX"endend# Usagedef mainopengl_circle = OpenGLCircle.new(10, 20, 15)opengl_rectangle = OpenGLRectangle.new(5, 5, 30, 40)directx_circle = DirectXCircle.new(10, 20, 15)directx_rectangle = DirectXRectangle.new(5, 5, 30, 40)opengl_circle.drawopengl_rectangle.drawdirectx_circle.drawdirectx_rectangle.drawendmain# Run the example# Output:# Rendering circle at (10, 20) with radius 15 using OpenGL# Rendering rectangle at (5, 5) with width 30 and height 40 using OpenGL# Rendering circle at (10, 20) with radius 15 using DirectX# Rendering rectangle at (5, 5) with width 30 and height 40 using DirectX
1๏ธโฃ Class Explosion: For every new shape or rendering API, a new class must be created. For n shapes and m rendering APIs, you would need n * m classes.
2๏ธโฃ Difficult Maintenance: If a rendering API's behavior changes, multiple classes need to be updated.
3๏ธโฃ Limited Extensibility: Adding new shapes or rendering APIs requires significant modifications, violating the Open-Closed Principle.
4๏ธโฃ Redundant Code: Similar logic is repeated across classes, leading to poor code reuse.
The Bridge pattern separates the abstraction (Shape) from the implementation (Renderer). This allows the two to evolve independently, providing flexibility and scalability.
By using a Bridge, you can introduce new shapes or rendering libraries without modifying existing code.

In this example:
๐น The Abstraction (Shape) defines a base interface for shapes.
๐น The Implementor (Renderer) specifies the rendering interface.
๐น Concrete Implementors (OpenGLRenderer, DirectXRenderer) provide platform-specific implementations.
๐น Refined Abstractions (Circle, Rectangle) extend the base shape with specific functionality.
1๏ธโฃ Define the abstraction and implementation as separate interfaces.
2๏ธโฃ Implement the implementation interface in concrete classes (e.g., OpenGLRenderer, DirectXRenderer).
3๏ธโฃ Create concrete classes for the abstraction that use the implementation through composition.
4๏ธโฃ The abstraction and implementation work together without being tightly coupled.
๐ก Note: This pattern adheres to the Open/Closed Principle, allowing extensions without modifying existing code.
๐ฎ Decoupling: Abstraction (shapes) and implementation (rendering APIs) are independent, enabling modifications in one without affecting the other.
๐ฎ Scalability: Adding new shapes or rendering APIs only requires creating new subclasses or implementations, avoiding class explosion.
๐ฎ Reusability: Common functionality is centralized, reducing redundancy.
๐ฎ Open-Closed Principle: The system is open for extension but closed for modification.
By applying the Bridge Pattern, we achieve a flexible and scalable design that can easily adapt to future changes and requirements.
# Implementor: Rendering interfaceclass Rendererdef render_circle(x, y, radius)raise NotImplementedError, "Subclasses must implement this method"enddef render_rectangle(x, y, width, height)raise NotImplementedError, "Subclasses must implement this method"endend# ConcreteImplementor: OpenGL implementationclass OpenGLRenderer < Rendererdef render_circle(x, y, radius)puts "Rendering circle at (#{x}, #{y}) with radius #{radius} using OpenGL"enddef render_rectangle(x, y, width, height)puts "Rendering rectangle at (#{x}, #{y}) with width #{width} and height #{height} using OpenGL"endend# ConcreteImplementor: DirectX implementationclass DirectXRenderer < Rendererdef render_circle(x, y, radius)puts "Rendering circle at (#{x}, #{y}) with radius #{radius} using DirectX"enddef render_rectangle(x, y, width, height)puts "Rendering rectangle at (#{x}, #{y}) with width #{width} and height #{height} using DirectX"endend# Abstraction: Base Shapeclass Shapedef initialize(renderer)@renderer = rendererendend# RefinedAbstraction: Circleclass Circle < Shapedef initialize(renderer, x, y, radius)super(renderer)@x, @y, @radius = x, y, radiusenddef draw@renderer.render_circle(@x, @y, @radius)endend# RefinedAbstraction: Rectangleclass Rectangle < Shapedef initialize(renderer, x, y, width, height)super(renderer)@x, @y, @width, @height = x, y, width, heightenddef draw@renderer.render_rectangle(@x, @y, @width, @height)endend# Example Usagedef mainopengl_renderer = OpenGLRenderer.newdirectx_renderer = DirectXRenderer.newcircle = Circle.new(opengl_renderer, 10, 20, 15)rectangle = Rectangle.new(directx_renderer, 5, 5, 30, 40)circle.drawrectangle.drawendmain# Run the example# Output:# Rendering circle at (10, 20) with radius 15 using OpenGL# Rendering rectangle at (5, 5) with width 30 and height 40 using DirectX
The Bridge pattern provides a powerful way to decouple abstraction from its implementation.
๐บ By using this pattern, you can add new shapes or rendering techniques without modifying existing classes, promoting flexibility and maintainability.
๐บ The Bridge pattern simplifies the codebase by avoiding tight coupling between abstractions and their implementations.
๐บ It adheres to the Open/Closed Principle, making it easier to extend functionality in the future.
๐ป You can find this and other design patterns here ๐