023
structural GOF

Bridge

Reference Wikipedia ↗
Bridge — class diagram
Plate 023 class diagram

The Bridge pattern is a structural design pattern that lets you split an interface into separate interfaces. This pattern is useful when you want to avoid a tight coupling between an abstraction and its implementation, allowing you to vary them independently. It’s particularly effective when you anticipate that both the abstraction and implementation will change in different ways.

In essence, the Bridge introduces an Implementor interface which provides the core functionality, and an Abstractor interface which uses the Implementor to deliver a higher-level abstraction. This decoupling allows for flexibility and extensibility. Different implementations can be swapped without affecting the abstraction, and vice versa.

Usage

The Bridge pattern is commonly used in the following scenarios:

  • Database Abstraction: When your application needs to work with different database systems (e.g., MySQL, PostgreSQL, Oracle), you can use the Bridge pattern to isolate the database-specific implementation details from the application’s core logic.
  • Graphics Rendering: When you have different rendering engines (e.g., OpenGL, DirectX, SVG), a Bridge pattern allows you to switch between them easily without altering the code that uses them.
  • Platform Independence: When application logic must be independent of the underlying operating system (Windows, macOS, Linux), the Bridge can separate platform-specific calls.
  • Message Queues: Using different message queue systems (RabbitMQ, Kafka, Redis Pub/Sub) requires the abstraction of the messaging implementation.

Examples

1. Java Virtual Machine (JVM)

The JVM internally uses a Bridge pattern. The Java language specification defines the Abstractor – the bytecode instructions and the Java API. The actual Implementor is the underlying native code execution environment, which differs for each operating system (Windows, macOS, Linux). The JVM bridges the gap between the platform-independent Java bytecode and the platform-dependent hardware instructions.

2. Remote Control with Different Protocols

Consider a remote control that can control different devices. The remote control’s button presses (Abstractor) need to be translated into specific commands for the device. The communication protocol (Implementor) – such as infrared, Bluetooth, or Wi-Fi – can be changed without needing to modify the remote control’s core logic. You could have a RemoteControl class paired with interfaces like InfraredCommandExecutor, BluetoothCommandExecutor, and WiFiCommandExecutor.

java // Implementor interface CommandExecutor { void execute(String command); }

class InfraredCommandExecutor implements CommandExecutor { @Override public void execute(String command) { System.out.println(“Sending infrared command: " + command); } }

class BluetoothCommandExecutor implements CommandExecutor { @Override public void execute(String command) { System.out.println(“Sending bluetooth command: " + command); } }

// Abstractor class RemoteControl { private CommandExecutor executor;

public RemoteControl(CommandExecutor executor) { this.executor = executor; }

public void pressButton(String command) { executor.execute(command); } }

// Example Usage public class BridgeExample { public static void main(String[] args) { RemoteControl irRemote = new RemoteControl(new InfraredCommandExecutor()); irRemote.pressButton(“channelUp”);

RemoteControl btRemote = new RemoteControl(new BluetoothCommandExecutor());
btRemote.pressButton("volumeDown");

} }

Specimens

15 implementations
Specimen 023.01 Dart View specimen ↗

The Bridge pattern is a structural design pattern that lets you split an abstraction from its implementation so that objects can have different implementations. This is useful when you want to avoid a tight coupling between abstract and concrete classes, allowing you to change implementations independently without affecting the abstraction.

The code demonstrates a Shape abstraction with Color implementations. Shape has a render() method that delegates to a Color object to actually do the coloring. Different Color implementations (e.g., RedColor, BlueColor) provide different coloring behaviors. This allows us to combine different shapes with different colors without creating a combinatorial explosion of shape-color classes. The use of interfaces (Color) and abstract classes (Shape) is idiomatic Dart for defining contracts and abstractions.

// Define the abstraction
abstract class Shape {
  Color color;

  Shape(this.color);
  void render();
}

// Define the implementation interface
abstract class Color {
  void applyColor();
}

// Concrete implementations
class RedColor implements Color {
  @override
  void applyColor() {
    print('Applying red color.');
  }
}

class BlueColor implements Color {
  @override
  void applyColor() {
    print('Applying blue color.');
  }
}

// Concrete abstractions
class Circle extends Shape {
  Circle(Color color) : super(color);

  @override
  void render() {
    print('Rendering a circle with ');
    color.applyColor();
  }
}

class Square extends Shape {
  Square(Color color) : super(color);

  @override
  void render() {
    print('Rendering a square with ');
    color.applyColor();
  }
}

// Usage
void main() {
  final redCircle = Circle(RedColor());
  redCircle.render();

  final blueSquare = Square(BlueColor());
  blueSquare.render();
}