Introduction
The Bridge Design Pattern is a Structural Design Pattern, which splits the abstraction from the implementation. In this article, we'll be covering the motivation and implementation of the Bridge Design Pattern in Python.
Design Patterns refer to a set of standardized practices or solutions to common architectural problems in software engineering.
Motivation Behind the Bridge Design Pattern
The Bridge Pattern prevents what's called the cartesian product complexity explosion.
The problem will be obvious going through an example. Suppose you're implementing an Airplane
. It can be a military or commercial airplane. Furthermore, it can be a passenger/soldier or cargo airplane.
One approach to implementing this is by having a MilitaryPassenger
, MilitaryCargo
, CommercialPassenger
and CommercialCargo
airplanes.
Here the cartesian product complexity is 2 x 2 = 4
. This number isn't ground-breaking at this scale, but when you include more classes and variations, it can rise exponentially and it'll very quickly become unmanageable.
The Bridge Pattern is used, well, as a bridge between classes (Airplane
implementation) and their characteristics (is it a passenger or cargo plane). It favors composition over inheritance.
Using the pattern, you create one class for each of the types in different categories. For instance, in our example, we'd have a CommercialPlane
and MilitaryPlane
as entities, with CargoCarrier
and PassengerCarrier
as separate entities.
It might not look like we've done a lot since we've still got 4 classes, but imagine this on a scale. You can have nPlane
classes and only have CargoCarrier
and PassengerCarrier
which you can apply to these planes.
An even better way would be to have parent classes - Carrier
and Plane
. For the Carrier
parent class, you can create two child classes: Cargo
and Passenger
. Similarly, for the Plane
parent class, two child classes: Military
and Commercial
.
Next, you will need a way to connect, or in other words, bridge Carrier
and Plane
subclasses. You can do so by passing one of these two classes as a parameter value in the constructor of the other class. By implementing the pattern, we'll be able to combine any of the subclasses.
Finally, let's see how we can implement the Bridge Design Pattern in Python.
Implementing the Bridge Design Pattern in Python
As I said earlier, we will create a parent class Carrier
with two abstract methods: carry_military()
, and carry_passenger()
. Next, we can create a child class Cargo
which inherits from the Carrier
class and implements the carry_military()
and carry_commercial()
methods.
To avoid the slippery slope of adding variations of classes, we'll define a Carrier
with two abstract methods - carry_military()
and cary_passenger()
.
The Carrier
class will additionally have two child classes - Cargo
and Passenger
which both inherit its abstract methods and implement them:
# Passenger & Cargo Carriers
class Carrier:
def carry_military(self, items):
pass
def carry_commercial(self, items):
pass
class Cargo(Carrier):
def carry_military(self, items):
print("The plane carries ", items," military cargo goods")
def carry_commercial(self, items):
print("The plane carries ", items," commercial cargo goods")
class Passenger(Carrier):
def carry_military(self, passengers):
print("The plane carries ", passengers , " military passengers")
def carry_commercial(self, passengers):
print("The plane carries ", passengers , " commercial passengers")
In the same vein, we'll create a Plane
class with two abstract methods - display_description()
and add_objects()
, as well as two child classes - Commercial
and Military
. We'll be passing a Carrier
to the constructor of the Plane
class. This is the bridge.
If the plane is a Commercial
plane, it's Cargo
and Passenger
will return carry_commercial()
, and vice versa.
The number of passengers/goods will be stored in the self.objects
variable which is passed as a parameter to the carry_commercial()
method:
# Military & Commercial Planes
class Plane:
def __init__(self, Carrier):
self.carrier = Carrier
def display_description(self):
pass
def add_objects(self):
pass
class Commercial(Plane):
def __init__(self, Carrier, objects):
super().__init__(Carrier)
self.objects = objects
def display_description(self):
self.carrier.carry_commercial(self.objects)
def add_objects(self, new_objects):
self.objects += new_objects
class Military(Plane):
def __init__(self, Carrier, objects):
super().__init__(Carrier)
self.objects = objects
def display_description(self):
self.carrier.carry_military(self.objects)
def add_objects(self, new_objects):
self.objects += new_objects
Our classes are primed and ready. Now is the time to create some objects and have them bridge between themselves through the previously mentioned constructor call.
Let's take a look at an example:
cargo = Cargo()
passenger = Passenger()
# Bridging Military and Cargo classes
military1 = Military(cargo , 100)
military1.display_description()
military1.add_objects(25)
military1.display_description()
Here, we've instantiated objects for the Cargo
and Passenger
classes. Then, in a constructor call to the Military
class, we've passed the cargo
instance. Since it's a military plane, the cargo is considered military cargo.
Therefore, the display_description()
method will print out details regarding military cargo. Furthermore, we've added another 25
objects on top of this load:
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
The plane carries 100 military cargo goods
The plane carries 125 military cargo goods
In the same vein, we can bridge the Military
and Passenger
classes as well:
cargo = Cargo()
passenger = Passenger()
# Bridging Military and Passenger classes
military2 = Military(passenger , 250)
military2.display_description()
military2.add_objects(10)
military2.display_description()
Naturally, the display_description()
method results in the number of military passengers we're carrying:
The plane carries 250 military passengers
The plane carries 260 military passengers
Similarly, we can bridge Commercial
and Passenger
:
# Bridging Commercial and Passenger
commercial1 = Commercial(passenger , 400)
commercial1.display_description()
commercial1.add_objects(50)
commercial1.display_description()
Which will result in:
The plane carries 400 commercial passengers
The plane carries 450 commercial passengers
And finally, we can bridge the Commercial
and Cargo
classes:
# Bridging Commercial and Cargo
commercial2 = Commercial(cargo, 150)
commercial2.display_description()
commercial2.add_objects(15)
commercial2.display_description()
Which results in:
The plane carries 150 commercial cargo goods
The plane carries 165 commercial cargo goods
Conclusion
The Bridge Design Pattern is a Structural Design Pattern, which splits the abstraction from the implementation. In this article, we've explored the motivation behind the Bridge Design Pattern and how it works.
Afterwards, we've implemented the pattern in Python, showcasing how the pattern works.