Bridge Pattern in Scala

Saurabh Kishore Tiwari
The Thought Mill
5 min readMay 7, 2023

--

Picture this: You’re building a bridge. Not just any bridge, mind you — this is going to be a masterpiece of engineering, a triumph of design, and a sight to behold. You’ve got the plans in front of you, the tools at your disposal, and a team of experts ready to get to work.

But wait a minute, something’s not quite right. You’re looking at those plans, and you’re starting to realize that there’s a problem. The bridge you’re building is supposed to connect two different places, but the way it’s designed, it’s only going to work for one of them. If you try to use it to get to the other side, you’re going to end up swimming with the fishes.

What do you do? Do you scrap the whole thing and start over? Do you try to retrofit it on the fly? Do you just hope that nobody notices the problem until it’s too late?

No, no, and no. What you need is the Bridge Design Pattern.

The Bridge Design Pattern is a way of separating an abstraction (the way something is used or interacted with) from its implementation (the nitty-gritty details of how it actually works). In the context of our bridge-building example, it means that we can have a bridge that connects two different places, even if those places have different requirements or constraints

In Scala, we can implement the Bridge Design Pattern using traits and abstract classes to define the abstractions and implementations, and then use a separate class (the Bridge object) to connect the two. This gives us the flexibility and modularity we need to build bridges that can handle whatever challenges we might face.

So there you have it — the Bridge Design Pattern in a nutshell. Whether you’re building a bridge, designing a software system, or just trying to make sense of a complicated problem, the Bridge Design Pattern can help you connect the dots and bridge the gap between abstraction and implementation.

Without wasting much time, let’s look at the UML diagram for the same

Image Cortsey — Geeks for Geeks

Let’s breakdown the structure now

Abstraction
1. Defines the abstraction’s interface.
2. Maintains a reference to an object of type Implementor.

RefinedAbstraction
1. Extends the interface defined by Abstraction.

Implementor
1. Defines the interface for implementation classes. This interface doesn’t have to correspond exactly to Abstraction’s Interface.

ConcreteImplmentor
1. Implements the Implementor interface and defines jits concrete implementation.

Implementation

Imagine you have a use-case, where you have 3 shapes — rectangle, triangle, and circle. You can create them in 3 different colors — Red, Blue, and Green. And you can render it in 2 different ways, one for IOS and one for Android.

You decide to write a separate class for each possible combination and end up with 18 classes: 3 colors * 3 shapes * 2 views.

But the next day, you decide to introduce two more shapes — square and parallelogram — and one more color — yellow. Now you’ll have to create a separate class for every possible combination, resulting in 40 classes

This is leading to class explosion

So instead, we’ve decided to use the Bridge pattern. Rendering something only requires a few details: color, shape, and view type. So, we’ll make these requirements traits.

We’ll have a trait for each of the three colors, and each will extend a base trait called ‘Color’. For shapes, we’ll have a trait called ‘Shape’ that is composed of color. And for view, we’ll have a trait called ‘View’ that is composed of shape. All shapes and views will extend their respective traits and can have their own implementations.

The UML diagram for this approach would look something like this:

So with this approach, we end up with only 8 classes and 3 interfaces. And if we ever need to add more colors or shapes, we can easily do so without creating a mess of new classes. It’s a scalable solution that will allow us to keep things simple and organized, no matter how complex our requirements become.

Code

Colors.scala

sealed trait Color:
val name: String
def getColor: String = name

class Red extends Color:
override val name = "Red"

class Green extends Color:
override val name: String = "Green"

class Blue extends Color:
override val name: String = "Blue"

Shapes.scala

sealed trait Shape(color: Color):
val name: String
def getDescription: String = s"This is $name of color ${color.getColor}"

class Triangle(color: Color) extends Shape(color):
override val name: String = "Triangle"

class Rectangle(color: Color) extends Shape(color):
override val name: String = "Rectangle"

class Circle(color: Color) extends Shape(color):
override val name: String = "Circle"

Views.scala

sealed trait View(shape: Shape):
val viewType: String
def render: String = s"${shape.getDescription} on $viewType"

class IOSView(resource: Shape) extends View(resource):
override val viewType: String = "iOS"

class AndroidView(resource: Shape) extends View(resource):
override val viewType: String = "Android"

MainRuner.scala

object MainRunner:
def main(args: Array[String]): Unit =
val triangle: Shape = Triangle(Red())
val rectangle: Shape = Rectangle(Green())
val circle: Shape = Circle(Blue())

val iosView: View = IOSView(triangle)
val androidView: View = AndroidView(rectangle)
val iosView1: View = IOSView(circle)

println(iosView.render)
println(androidView.render)
println(iosView1.render)

Output

This is Triangle of color Red on iOS
This is Rectangle of color Green on Android
This is Circle of color Blue on iOS

Known Usage and Related Patterns

Remember back in Windows XP when we used to open Paint and draw all sorts of things? Well, it turns out they were using the Bridge Pattern. And it’s not just limited to Paint.

Here are some other examples of the Bridge Pattern in use:

  • Operating Systems: Windows, macOS, and Linux all use the Bridge pattern to separate their user interfaces from their underlying functionality. The graphical user interface serves as the abstraction, while the operating system’s internal functionality serves as the implementation.
  • Payment Gateways: Payment gateways like PayPal use the Bridge pattern to connect payment processing systems to online merchants. The payment gateway’s interface serves as the abstraction, while the payment processing system serves as the implementation.
  • Cars: Car manufacturers use the Bridge pattern to separate a car’s body style and design from its engine and mechanical components.

It’s worth noting that the Abstract Factory Pattern and the Adapter Pattern are similar to the Bridge Pattern.

--

--