The Bridge Pattern belongs to the Structural category of design patterns.
Why? Because it helps us split two related objects so they can grow independently, without stepping on each other’s toes.
Think of it like using a Universal Remote. With this you can control different brands of TVs, sound systems, or streaming boxes, without buying a separate remote for each.
That is because the remote and the device work independently but together through a common connection.
The Music Connector
Say you have just bought a brand new smart speaker. Party time indeed !!
It looks sleek, sounds amazing, and you can’t wait to blast your favorite playlist.
But here’s the catch,
You are a Spotify person, your partner loves Apple Music, and your friend is always on YouTube Music.
Now imagine if the manufacturer had to build a different speaker for every streaming service, like,
- Smart Speaker + Spotify
- Smart Speaker + Apple Music
- Smart Speaker + YouTube Music
… and the same for phones, car stereos, and every other music device.
That would be absolute madness. We would end up having a storage room full of almost identical devices, each tied to just one service.
Thankfully, that is not how it works in real life.
Instead, your device (phone, speaker, car stereo) and your music service (Spotify, Apple Music, YouTube Music) are independent, yet they work together seamlessly. You can mix and match however you like.
That is the Bridge Pattern in action.
It is the “connector” that lets two separate worlds (in our example - devices and services) interact without being locked into each other.
What is the Bridge Pattern?
The Bridge Pattern helps you separate an abstraction from its implementation so that both can evolve independently.
In our music example,
Abstraction → The device (Phone, Smart Speaker, Car Stereo)
Implementation → The streaming service (Spotify, Apple Music, YouTube Music)
Instead of creating every possible combination as a separate class, we “bridge” the two so they can connect dynamically.
What does the code look like?
// Step 1: Define the "Implementor" interface (MusicSource)
Interface MusicSource
Method PlayMusic()
// Step 2: Concrete Implementations of MusicSource
Class Spotify implements MusicSource
Method PlayMusic()
Print "Playing music from Spotify..."
Class YouTubeMusic implements MusicSource
Method PlayMusic()
Print "Playing music from YouTube Music..."
Class AppleMusic implements MusicSource
Method PlayMusic()
Print "Playing music from Apple Music..."
// Step 3: Define the "Abstraction" (Device)
Class MusicDevice
Property musicSource // reference to a MusicSource
Constructor(musicSource)
this.musicSource = musicSource
Method Play()
musicSource.PlayMusic()
// Step 4: Refined Abstractions (Specific Devices)
Class Phone extends MusicDevice
Constructor(musicSource)
super(musicSource)
Class CarStereo extends MusicDevice
Constructor(musicSource)
super(musicSource)
Class SmartSpeaker extends MusicDevice
Constructor(musicSource)
super(musicSource)
//caller logic
spotify = new Spotify()
youtube = new YouTubeMusic()
phone = new Phone(spotify)
phone.Play() // Output: Playing music from Spotify...
carStereo = new CarStereo(youtube)
carStereo.Play() // Output: Playing music from YouTube Music...
Here is how the above code works,
You (the caller) decide which Device (e.g., SmartSpeaker, CarStereo) you want and which Music Source (e.g., SpotifyService, YouTubeMusicService) it should play from.
When creating the device, you pass the chosen music source into the device’s constructor. This is the “bridge” that connects the two worlds.
Device (Abstraction): The device defines high level actions like
PlayMusic()
orStopMusic()
. It does not know how the music service works, instead, it just calls the music service’s methods.Music Service (Implementor): Each music service (Spotify, YouTube Music, etc.) implements its own logic for playing/streaming music.
The device simply delegates to this service without caring about its details.
In short,
You call myDevice.Play()
→ The device calls musicSource.PlayMusic()
internally → The music service does its work and returns control back to the device.
What Did We Achieve?
With this approach, the devices and music services are separate. You can add a new device without touching any music service code, or add a new music service without touching any device code. The constructor injection is the handshake that makes them work together without being tightly coupled.
When Should You Use the Bridge Pattern?
- When you have two dimensions of change, for example, different devices and different music sources, and you want them to evolve independently.
- When subclass explosion is a risk. Without Bridge, you would need a class for every combination (e.g. CarStereoWithSpotify, CarStereoWithYouTubeMusic, etc.).
- When you want loose coupling between high level logic (device) and low level details (music service), so changes on one side don’t break the other.
- When future scalability matters. You can add new variations of either side without touching the other side’s code.
Use Cases?
- Payment systems (different payment methods + different platforms)
- UI Themes (different themes + different components)
- Messaging apps (different message senders + different protocols)
To summarize, it would be apt to say,
The Bridge Pattern is like using a universal remote that works with different brands of TVs, separating the remote (abstraction) from the TV (implementation) so both can evolve independently without messing each other up.
Hope this made the Bridge Pattern easy to grasp!
Next up: Composite Pattern. See you there!
Top comments (0)