The bridge structural pattern allows you to separate an interface from its implementation. Maybe you want to start out doing something one way and then change later. Or maybe you want to share an implementation. Or, if you’re using C++, you can hide how you’re doing something. This is a subtle pattern with a lot of potential.
Maybe you’re creating an application with vehicles in it. This could be a racing game or a driver certification application. You create a base class called Vehicle and derived classes Car, Truck, and Tractor which each inherit from the Vehicle class. Then you need different kinds of engines and since there’s a difference in behavior, you plan to create more classes to specialize. The problem is that not only does this lead to a lot of classes such as GasCar, DieselCar, GasTruck, DieselTruck, and DieselTractor, but you can’t change the type of engine in a car.
Why would you need to do that? Cars always have the same engine, right? Even in real life, vehicles can sometimes change. And this is software. Everything changes in software.
Instead of creating so many derived classes, a better solution will use the bridge pattern to form a link between the basic vehicle types and the engine types. This allows both vehicles and engines to be subclassed or derived from independently of each other. and because this pattern uses composition instead of inheritance, you can easily swap an engine type for another.
Listen to the full episode for more about this pattern or read the full transcript below.
The basic description says that this pattern lets abstractions decouple from implementations so they can both change independently. You learned about the adapter pattern yesterday which lets you use classes that weren’t designed to be used together. Well, this pattern, lets classes that are designed to be used separately be used together and mixed around if you want.
This is a difficult pattern to understand without an example so let’s say you’re building a game with vehicles in it. You have cars, trucks, and tractors so you create an abstract class called Vehicle to represent all vehicles and you declare the classes Car, Truck, and Tractor to each derive from Vehicle. So far everything is going well.
But then you realize that your vehicles can have either a gasoline engine or a diesel fuel engine with different behaviors each. Now what do you do?
You could create some more subclasses. This would give you a GasCar, DieselCar, GasTruck, DieselTruck, and you go with just DieselTractor because you’ve never heard of a gasoline powered tractor. The number of classes is already increasing. Then you remember electric engines and create yet more classes. Hydrogen fuel cells? You got it. More classes.
And just what are you going to do about a car that starts out with a gasoline engine until some engineer modifies it to use a futuristic capacitor instead? That won’t happen, right?
This whole example is just made up. Yet this is exactly the sort of decisions you need to make as a software developer. Because you don’t want to be responsible for a design that works well for version one, then requires a complete re-write for version two. It’ll happen if you don’t plan for flexibility and fail to incorporate design patterns like this that have been proven over and over to represent some really good approaches.
I’ll explain how you can improve the vehicle and engine dilemma by using the bridge pattern right after this message from our sponsor.
( Message from Sponsor )
The big problem with using inheritance, other than the huge number of classes you end up with, is that inheritance fixes or locks in place the implementation at design time. Sure, you get excellent types that are unique and specific but they become set in stone. You’ll need to come up with a whole new FirstGasThenFusionCar class when really all that’s happening is that the car needs to switch its implementation from gas to fusion. Just think of hybrid cars that go back and forth all the time between using gas and using electric.
Class inheritance is just too rigid for this example. Instead, let’s go back to just the original classes where we had the base Vehicle class and separate derived classes called Car, Truck, and Tractor.
Then let’s create another base class, or interface depending on your language, that defines all the functionality of an engine in general terms. This will be an abstract class called VehicleImpl. The name might seem strange at first. It’s just Vehicle followed by the letters I m p l which is short for implementation. I mean, sure, if you want to call this class VehicleImplementation, go ahead. When you get tired of typing that many characters all the time, you’ll understand why it’s common to shorten this to just VehicleImpl.
Then, the Vehicle class will have a pointer or contain a reference to a VehicleImpl. Remember, the VehicleImpl class is abstract so we’ll need some concrete classes to actually work with. I’ll get to that in just a moment. for now though, focus on how the Vehicle class can use the VehicleImpl instance that it contains. This forms a bridge between the two classes. Anytime a car wants to go faster, it calls a method declared in the base Vehicle class to change the speed. This method has no idea how to change the speed. It doesn’t have a clue what kind of engine is in the car. But it does know about the bridge to the VehicleImpl member instance and it calls a similar method to change the speed in the VehicleImpl class.
This is where we need specific classes that derive from VehicleImpl for each type of engine. so there’s a GasVehicleImpl class, a DieselVehiclImpl class, and an ElectricVehicleImpl class.
Now, you might be wondering right about now where that class savings went. I thought we were supposed to cut down on the number of classes. Yet here we are creating yet more classes.
This is true. We are creating more classes. But here’s the important part. We’ve eliminated all the combinations of classes. All we need now is a single class for each vehicle type and another single class for each engine type. Then we can mix and match these types as needed. We can even swap them out at runtime which we couldn’t do with inheritance.
Now when the time comes to add that fusion engine, you only need to add a FusionVehicleImpl class and your code at least is ready. Whether or not society is ready for flying cars is a different question. At least you don’t need to explain to your boss why adding a new engine type suddenly means an extra six months of development time.
Thank you for listening. Make sure to check out the other episodes and if you get value, please give the Take Up Code podcast a rating (hopefully 5 stars) and a review. I’ll select several people who take the time to provide thoughtful reviews, good or bad, and give you a shout out and a big thank you during our Q&A Fridays.
Your support goes a long way to helping this podcast thrive and help others learn how to program.
If you have any questions about this episode or even other programming questions, please ask. Just go to takeupcode.com/contact and submit your question. The Q&A Friday episodes are where I’ll answer one or more submitted questions.
Until next time, keep learning. But even better, keep doing. If you want to learn how to program, then start programming. And you’ll learn and get better and your mind will expand in ways you never thought possible.