3.2. Bridge

Maheshmaddi
4 min readApr 9, 2023

--

The Bridge pattern is a structural design pattern that decouples an abstraction from its implementation, allowing the two to vary independently. This pattern is useful when the abstraction and its implementation have different requirements or constraints, and it helps prevent the proliferation of subclasses or classes with a high degree of coupling.

The Bridge pattern is typically used when:

  1. You want to separate an abstraction from its implementation, allowing the two to evolve independently.
  2. You want to share an implementation among multiple objects without creating a strong link between them.

To implement the Bridge pattern, follow these steps:

  1. Define an interface or abstract class for the abstraction, which includes a reference to the implementation.
  2. Create concrete classes for the abstraction that extend the abstract class or implement the interface.
  3. Define an interface or abstract class for the implementation.
  4. Create concrete classes for the implementation that extend the abstract class or implement the interface.

Here’s a simple example of the Bridge pattern in Java:

// Abstraction interface
public interface Abstraction {
void operation();
}

// Implementor interface
public interface Implementor {
void implementation();
}

// Concrete implementors
public class ConcreteImplementorA implements Implementor {
@Override
public void implementation() {
System.out.println("ConcreteImplementorA implementation");
}
}

public class ConcreteImplementorB implements Implementor {
@Override
public void implementation() {
System.out.println("ConcreteImplementorB implementation");
}
}

// Refined abstraction
public class RefinedAbstraction implements Abstraction {
private Implementor implementor;

public RefinedAbstraction(Implementor implementor) {
this.implementor = implementor;
}

@Override
public void operation() {
implementor.implementation();
}
}

// Client code
public class Client {
public static void main(String[] args) {
Implementor implementorA = new ConcreteImplementorA();
Abstraction abstractionA = new RefinedAbstraction(implementorA);
abstractionA.operation(); // Outputs: "ConcreteImplementorA implementation"

Implementor implementorB = new ConcreteImplementorB();
Abstraction abstractionB = new RefinedAbstraction(implementorB);
abstractionB.operation(); // Outputs: "ConcreteImplementorB implementation"
}
}

In this example, the Abstraction interface represents the abstraction, and the Implementor interface represents the implementation. The RefinedAbstraction class implements the Abstraction interface and contains a reference to an Implementor instance. The ConcreteImplementorA and ConcreteImplementorB classes implement the Implementor interface, providing different implementations. The client code can use different combinations of abstractions and implementations without modifying the source code of either.

Advantages of the Bridge pattern:

  1. Decoupling: The Bridge pattern decouples the abstraction from its implementation, allowing them to evolve independently and reducing the risk of tightly coupled code.
  2. Extensibility: The pattern makes it easy to extend the abstraction and implementation independently, promoting a cleaner and more modular codebase.

Disadvantages of the Bridge pattern:

  1. Increased complexity: The Bridge pattern introduces additional classes and interfaces, which can increase the overall complexity of the code.
  2. Indirection: The use of the Bridge pattern can introduce a level of indirection that may make the code harder to understand and maintain.

When using the Bridge pattern, consider its benefits and drawbacks carefully. Use the pattern when you want to separate the abstraction from its implementation or when you want to share an implementation among multiple objects. Be aware of the potential complexity and indirection that may be introduced by the pattern.

Use case: Music Streaming Application using the Bridge Pattern

Class diagram for Musing Streaming application using Bridge Pattern

In a music streaming application, we can use the Bridge Pattern to decouple the abstraction (the user interface) from the implementation (the music player). This allows us to vary the implementation of the music player without affecting the user interface.

Here’s a sample Java code implementation:

// Abstraction interface
interface MusicPlayer {
void playMusic(String song);
void stopMusic();
}

// Refined abstraction interface
class AdvancedMusicPlayer implements MusicPlayer {
private MusicPlayerImplementation implementation;

public AdvancedMusicPlayer(MusicPlayerImplementation implementation) {
this.implementation = implementation;
}

@Override
public void playMusic(String song) {
implementation.play(song);
}

@Override
public void stopMusic() {
implementation.stop();
}
}

// Implementation interface
interface MusicPlayerImplementation {
void play(String song);
void stop();
}

// Concrete implementation
class SpotifyPlayer implements MusicPlayerImplementation {
@Override
public void play(String song) {
System.out.println("Playing " + song + " on Spotify.");
}

@Override
public void stop() {
System.out.println("Stopping music on Spotify.");
}
}

// Concrete implementation
class AppleMusicPlayer implements MusicPlayerImplementation {
@Override
public void play(String song) {
System.out.println("Playing " + song + " on Apple Music.");
}

@Override
public void stop() {
System.out.println("Stopping music on Apple Music.");
}
}

// Client code
public class MusicStreamingApp {
public static void main(String[] args) {
MusicPlayerImplementation spotifyPlayer = new SpotifyPlayer();
MusicPlayerImplementation appleMusicPlayer = new AppleMusicPlayer();

MusicPlayer player1 = new AdvancedMusicPlayer(spotifyPlayer);
player1.playMusic("Song 1");
player1.stopMusic();

MusicPlayer player2 = new AdvancedMusicPlayer(appleMusicPlayer);
player2.playMusic("Song 2");
player2.stopMusic();
}
}

In this example, the MusicPlayer interface is the abstraction, and the MusicPlayerImplementation interface is the implementation. The AdvancedMusicPlayer class is the refined abstraction that takes an implementation object in its constructor. The SpotifyPlayer and AppleMusicPlayer classes are the concrete implementations.

By using the Bridge Pattern, we can switch the implementation of the MusicPlayer interface without affecting the AdvancedMusicPlayer class or the client code that uses it. For example, we could add a new MusicPlayerImplementation class for a different music streaming service, and simply pass it to an instance of AdvancedMusicPlayer.

Note: For complete list of design patterns click here

--

--