Bridge Design Pattern in Python
This story is part of the “Design Patterns” series. You can find the other stories of this series here:
You can also find all the code used through this series on GitHub.
The Bridge design pattern is a structural pattern that divides an object’s interface from its implementation, allowing the two to vary independently. This pattern is used to decouple an abstraction from its implementation so that the two can vary independently.
The Bridge design pattern is often used when you want to create a relationship between two classes that are structurally unrelated but share some kind of functionality. The pattern allows you to create a “bridge” between the two classes, which allows you to use the functionality of one class in the other class.
Problems the Bridge can Solve
Suppose you have a class called Shape that represents a geometric shape. The Shape class has a method called draw that takes a Canvas object as an argument and draws the shape on the canvas. The Canvas class has a method called draw_line that draws a line on the canvas.
Here is the initial implementation of the Shape and Canvas classes:
class Shape:
def draw(self, canvas):
pass
class Canvas:
def draw_line(self, x1, y1, x2, y2):
print(f"Drawing line from ({x1}, {y1}) to ({x2}, {y2})")
Now suppose you want to add support for drawing shapes in different colors. You could do this by adding a color attribute to the Shape class and modifying the draw method to use this attribute when drawing the shape. However, this approach has several problems:
- The
Shapeclass is now tightly coupled to theCanvasclass, because it directly calls thedraw_linemethod of theCanvasclass. This makes it difficult to change the implementation of theShapeclass without affecting theCanvasclass. - The
Shapeclass is now responsible for both the abstraction (the geometric shape) and the implementation (the color of the shape). This makes theShapeclass more complex and harder to understand.
class Shape:
def __init__(self, color):
self.color = color
def draw(self, canvas):
canvas.draw_line(1, 1, 2, 2, self.color)
class Canvas:
def draw_line(self, x1, y1, x2, y2, color):
print(f"Drawing line from ({x1}, {y1}) to ({x2}, {y2}) with color {color}")
if __name__ == "__main__":
canvas = Canvas()
shape = Shape("red")
shape.draw(canvas)Solution
To solve these problems, you can use the Bridge design pattern to decouple the Shape class from the Canvas class. Here is how you could refactor the code using the Bridge design pattern:
class Shape:
def __init__(self, color):
self.color = color
class LineShape(Shape):
def draw(self, canvas):
canvas.draw_line(1, 1, 2, 2, self.color)
class Canvas:
def draw_line(self, x1, y1, x2, y2, color):
print(f"Drawing line from ({x1}, {y1}) to ({x2}, {y2}) with color {color}")
if __name__ == "__main__":
canvas = Canvas()
shape = LineShape("red")
shape.draw(canvas)In this refactored design, the Shape class represents the abstraction (the geometric shape), and the LineShape class represents the implementation (the color of the shape). The LineShape class is responsible for drawing the shape on the canvas, using the color attribute of the Shape class. This decouples the Shape class from the Canvas class, allowing the two to vary independently.
Now, you will probably say that the LineShape class is coupled with the Canvas class. And that’s true. But where is the problem?
The LineShape class is the implementation. It doesn’t represent the abstraction, so it’s not a problem.
This pattern is a bit tricky to understand, let’s check another example.
Suppose you are building a system that allows users to send messages to each other. The system supports multiple message delivery channels, such as email, SMS, and push notifications.
You could use the Bridge design pattern to decouple the message delivery from the message itself:
class Message:
def __init__(self, text, delivery_channel):
self.text = text
self.delivery_channel = delivery_channel
def send(self):
self.delivery_channel.send(self.text)
class EmailDelivery:
def send(self, text):
# Code to send an email with the specified text
pass
class SMSDelivery:
def send(self, text):
# Code to send an SMS with the specified text
pass
class PushNotificationDelivery:
def send(self, text):
# Code to send a push notification with the specified text
passThe Message class represents the abstraction (the message to be sent), and the EmailDelivery, SMSDelivery, and PushNotificationDelivery classes represent the implementation (the delivery channel for the message). The Message class is decoupled from the delivery channel implementation, because it only calls the send method of the delivery_channel object, without knowing the specifics of how the message is delivered.
This design allows you to use the same message in different delivery channels, without having to modify the message itself. For example, you could create a message and send it via email, SMS, and push notification.
The code without using the Bridge pattern would look like this:
class Message:
def __init__(self, text, delivery_channel):
self.text = text
self.delivery_channel = delivery_channel
def send_via_email(self):
# Code to send an email with the specified text
pass
def send_via_sms(self):
# Code to send an SMS with the specified text
pass
def send_via_push_notification(self):
# Code to send a push notification with the specified text
passApplication
- When you want to use the same functionality in different contexts, and you want to be able to change the implementation without affecting the abstraction.
- When you want to decouple an abstraction from its implementation, allowing the two to vary independently.
- When you want to reduce the complexity of a system by separating the abstraction from the implementation.
- When you want to make a system more maintainable and easier to understand by focusing on one aspect of the system at a time.
Advantages
- Decouples an abstraction from its implementation, allowing the two to vary independently.
- Makes the system more maintainable and easier to understand if you take the time to understand the additional classes.
- Increases flexibility, as it allows you to change the implementation without affecting the abstraction.
Disadvantages
- Requires the creation of additional classes, which can eventually make the system more difficult to understand.
- Introduces a slight performance overhead due to the additional layer of abstraction
Final Note
It’s important to note that the Bridge pattern is not a one-size-fits-all solution, and you should only use it when it makes sense for your specific use case.
As with any design pattern, it’s important to carefully consider the trade-offs and decide whether the benefits of using the pattern outweigh the added complexity and overhead.
To explore the other stories of this story, click below!
To explore more of my Python stories, click here! You can also access all my content by checking this page.
If you want to be notified every time I publish a new story, subscribe to me via email by clicking here!
If you’re not subscribed to medium yet and wish to support me or get access to all my stories, you can use my link:





