The Bridge design pattern is a structural pattern that decouples an abstraction from its implementation, allowing the two to vary independently. It is particularly useful when you want to avoid a permanent binding between an abstraction and its implementation, enabling flexibility and extensibility.
Real-World Example:
Imagine you are designing remote controls for TVs. Instead of creating a unique remote control for every TV brand, you create a generic remote (abstraction) that works with a variety of TV brands (implementations). This way, new TV brands or remote control designs can be added without altering existing ones.
The Bridge pattern separates the abstraction (high-level logic) from its implementation (low-level details), allowing both to evolve independently.
Example:
Letβs implement a scenario with different device types (e.g., TV and Radio) and various remote controls to operate them.
// Implementor Interface
trait Device {
def isEnabled: Boolean
def enable(): Unit
def disable(): Unit
def getVolume: Int
def setVolume(volume: Int): Unit
}
// Concrete Implementor 1
class TV extends Device {
private var enabled: Boolean = false
private var volume: Int = 50
def isEnabled: Boolean = enabled
def enable(): Unit = { enabled = true; println("TV is now ON") }
def disable(): Unit = { enabled = false; println("TV is now OFF") }
def getVolume: Int = volume
def setVolume(volume: Int): Unit = { this.volume = volume; println(s"TV volume set to $volume") }
}
// Concrete Implementor 2
class Radio extends Device {
private var enabled: Boolean = false
private var volume: Int = 30
def isEnabled: Boolean = enabled
def enable(): Unit = { enabled = true; println("Radio is now ON") }
def disable(): Unit = { enabled = false; println("Radio is now OFF") }
def getVolume: Int = volume
def setVolume(volume: Int): Unit = { this.volume = volume; println(s"Radio volume set to $volume") }
}
// Abstraction
abstract class RemoteControl(protected val device: Device) {
def togglePower(): Unit = {
if (device.isEnabled) device.disable()
else device.enable()
}
def volumeUp(): Unit = device.setVolume(device.getVolume + 10)
def volumeDown(): Unit = device.setVolume(device.getVolume - 10)
}
// Refined Abstraction
class AdvancedRemoteControl(device: Device) extends RemoteControl(device) {
def mute(): Unit = {
device.setVolume(0)
println("Device is muted")
}
}
// Usage
object BridgePatternExample extends App {
val tv = new TV
val radio = new Radio
val tvRemote = new RemoteControl(tv)
val radioRemote = new AdvancedRemoteControl(radio)
// Using TV remote
tvRemote.togglePower() // TV is now ON
tvRemote.volumeUp() // TV volume set to 60
// Using Radio remote
radioRemote.togglePower() // Radio is now ON
radioRemote.mute() // Device is muted
}
Output:
TV is now ON
TV volume set to 60
Radio is now ON
Device is muted
When to Use:
- When you want to avoid a strong dependency between an abstraction and its implementation.
- When both the abstraction and the implementation need to vary independently.
- When you have multiple abstractions and implementations that can be combined dynamically.
References: