The Bridge Design Pattern with Python

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:

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.

Author image
About Usman Malik
Paris (France) Twitter
Programmer | Blogger | Data Science Enthusiast | PhD To Be | Arsenal FC for Life