Bridge Design Pattern in Ruby

1. Definition

The Bridge Design Pattern decouples an abstraction from its implementation so that the two can vary independently. The pattern splits a large class or a set of closely related classes into two separate hierarchies—abstraction and implementation—which can be developed independently of each other.

2. Problem Statement

Imagine a system designed to render shapes. If you have different shapes and rendering mechanisms, combining each shape with each rendering mechanism leads to a combinatorial explosion.

3. Solution

The Bridge pattern suggests moving the shared rendering functionality to a separate class hierarchy, leaving the original classes with high-level shape operations. This structure makes it easier to add new shapes or rendering mechanisms without altering existing code.

4. Real-World Use Cases

1. GUI libraries: Decoupling of window objects from operating system-specific window drawings.

2. Device drivers: Connecting software to specific pieces of hardware.

3. Remote control for devices: The implementation details for each device are encapsulated separately from the abstraction of the remote control.

5. Implementation Steps

1. Split the monolithic class into two parts: the abstraction and the implementation.

2. Create an interface for the implementation. All concrete implementations should follow this interface.

3. The abstraction class should reference the implementation interface.

4. Concrete abstractions are extended from the main abstraction.

5. The client should configure the abstraction and implementation at runtime.

6. Implementation in Ruby

# Step 2: Implementation Interface
module Renderer
  def render_shape(shape)
    raise NotImplementedError, 'Subclasses must define `render_shape`.'
  end
end
# Concrete Implementations
class VectorRenderer
  include Renderer
  def render_shape(shape)
    "Rendering #{shape} as vector."
  end
end
class RasterRenderer
  include Renderer
  def render_shape(shape)
    "Rendering #{shape} as raster."
  end
end
# Step 3 & 4: Abstraction
class Shape
  def initialize(renderer)
    @renderer = renderer
  end
  def draw
    @renderer.render_shape(self.class.name)
  end
end
# Concrete Abstractions
class Circle < Shape; end
class Square < Shape; end
# Step 5: Client Code
vector_renderer = VectorRenderer.new
circle = Circle.new(vector_renderer)
puts circle.draw
raster_renderer = RasterRenderer.new
square = Square.new(raster_renderer)
puts square.draw

Output:

Rendering Circle as vector.
Rendering Square as raster.

Explanation:

1. Renderer serves as an implementation interface containing the render_shape method, which must be implemented by all concrete renderers.

2. VectorRenderer and RasterRenderer are concrete implementations that render shapes in vector and raster forms respectively.

3. The Shape class serves as the abstraction. It uses the renderer's render_shape method to draw itself.

4. Circle and Square are concrete abstractions representing specific shapes.

5. The client code demonstrates how the abstraction (Shape) can be combined with any implementation (Renderer) to achieve the desired behavior.

7. When to use?

The Bridge pattern is particularly useful when:

1. You want to avoid a permanent binding between an abstraction and its implementation.

2. Abstractions and implementations should be extensible independently.

3. Changes in the implementation of an abstraction shouldn't impact client code.

4. You want to hide implementation details from the client.


Comments