Let me ask you a question?
Do you really understand design patterns—you know, the ones in that old Gang of Four book?
Perhaps you aren’t even really familiar with the term “design patterns.” It’s Ok, you are not alone. Design patterns are simply formal names given to common patterns that seem to emerge from solving various architectural problems in software development.
The term “design pattern” became popular after the book, by the same name was published was back in 1994. This landmark book is now affectionately referred to as the “Gang of Four” book, after the 4 authors who co-wrote the book: Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides.
As good as the book is, it does have one major short-coming. It’s a bit difficult to understand.
Perhaps though, you already understand design patterns, but do you really get them at a deep level? For the longest time in my career, I didn’t. Sure, I understood the basics, and I knew what Singleton was, but I didn’t really understand the how and why of most of the somewhat complicated design patterns.
I finally decided that enough was enough and I was going to scour the internet and books to find some simple explanations and examples of the design patterns that weren’t so easily explained.
Well, after all that searching, guess what? I didn’t find what I was looking for. I found plenty of explanations of design patterns, but none of them were very simple. I found plenty of examples of the various design patterns, but most of them were the same old tired examples that existed everywhere else.
So, I decided that I was going to go through all that information and come up with my own examples, over and over again until I really, deeply understood all of the design patterns. The process was long—and somewhat boring—but, I when I was finished I was able to confidently say that I finally really understood design patterns.
Sharing the Knowledge
In this series of articles, I’m going to take my lessons learned from going through all of the design patterns in depth and I am going to do my very best job to assimilate all that information into what I hope will be the most clear and easily understandable explanation of each of the patterns, starting with the Bridge pattern.
In this article, I will follow this format:
- Formal definition of the design pattern
- Broken down definition in plain English
- A real world example to let you think outside of the code
- An existing code-oriented example that would benefit from the design pattern
- A refactoring of the existing example to the pattern
- An explanation of the official UML diagram of the pattern relating it to the example
- When you should use the pattern
My hope is that by the time you read this article you will have in your mind not only a clear picture of what the Bridge pattern is and how it is implemented, but the real problem it is trying to solve and plenty of examples of where and how it can be used.
The Bridge Pattern
The pattern, I’ll be covering in this article will be the bridge pattern. This is a pattern that many developers—experienced and inexperienced alike—struggle with. Most developers I’ve talked to can only site the over-used example of how the pattern solves the problem of multiple operating systems and drawing APIs for those operating systems. I don’t know about you, but I am tired of that rarely useful example. I think we can do better.
Let’s start with the format definition, directly from the GoF book, Design Patterns.
“Decouples an abstraction from its implementation so that the two can vary independently.”
What the heck does that even mean? Let’s break it down.
Breaking it Down
In order to break down this definition we need to first make sure we understand what is meant by an abstraction and an implementation—and it’s not what you think.
In many programming languages such as C# or Java, an abstraction is thought of as an interface or an abstract class. And implementation is usually a structural implementation of that interface or a concrete class that inherits from and abstract base class.
Remember, the GoF definition is not language specific. This mostly affects the word interface—which is the most common point of confusion.
Forget about words and what they mean in specific programming languages for a minute and think about what is trying to be conveyed here instead.
We want to focus on the words themselves, not what they mean in a particular programming language.
Let’s start with abstraction. What is an abstraction?
It’s a generalized way of looking at something that makes it so you don’t have to know the details of how that thing is implemented.
A car steering wheel is an abstraction on top of a car. The car steering wheel abstracts away what is really going on to make a car change direction.
We don’t really care how it does it. In fact, the abstraction of the car steering wheel protects us from having to worry about all the details—especially if those details might change in the future.
Good news, in defining an abstraction, we also defined an implementation.
An implementation is just a realization of that abstraction. It’s what’s going on under the hood. It’s taking an abstraction and making it real.
Now, normally an abstraction and its implementation are coupled.
A car might have a car steering wheel abstraction, but the implementation of that car steering wheel is tied directly to that abstraction.
A car might utilize the car steering wheel abstraction, but it has a specific implementation of that abstraction and that implementation is directly tried to that abstraction.
But what if you had multiple kinds of vehicles that you wanted to be able to utilize the car steering wheel abstraction?
What if you had boats and airplanes that needed to utilize a steering wheel?
A car has a much different implementation of steering than a boat or an airplane, but all of those vehicles could have a similar looking steering wheel that all operate in a similar fashion.
You could put a car steering wheel in each of these vehicles, but if the abstraction of the car steering wheel is directly tied to the specific implementation for a car, it’s not going to work out very well. Steering a car might look the same as steering an airplane, but they work very differently.
What you need to do is to be able to decouple the abstraction of the steering wheel from its specific implementation for steering a car. You need an abstraction of an abstraction. You need to be able to abstract the already abstract idea of a car steering wheel into a more generalized abstraction of a vehicle steering wheel.
That is where the pattern comes in.
The pattern allows us to create a double abstraction—two layers of abstraction.
First we have the abstraction of the interface of the steering system for a car, represented by the car steering wheel.
But, then we take that abstraction one step further. We decouple the specific implementation of steering a car from the abstraction of a car steering wheel and create a higher level abstraction, a vehicle steering wheel.
Now, we can have multiple implementations of this simplified idea, or abstraction, of a vehicle steering wheel.
The vehicle steering wheel becomes our high level abstraction and the car steering wheel becomes the implementor of that abstraction. We can now create other implementations of the abstraction. We can create boat and airplane steering wheels, each with their own specific implementations.
Now we can create different steering wheels for each type of vehicle that aren’t coupled to a specific steering system. The pattern makes it so that the abstraction of a steering wheel is decoupled from the implementation of the specific steering systems, like using turning an axle or raising or lowering a wing-flap.
(I also wanted to mention another good book that explains design patterns in a fairly simple way. Check out Head First Design Patterns. It's one of the books I recommend in my Ultimate List of Programming Books.)
A Mode Code-Specific Example
Now, that we understand the pattern at a high level, let’s take what we learned and apply it to a software development domain.
Suppose you are creating a web application framework that will allow you to create different kinds of web applications.
Perhaps you want to be able to use the framework to create blog, news sites, stores and other kinds of web applications.
You could define a base web application class that could be the parent of all the different kinds of web applications, and everything would be fine until you decided to introduce the concept of themes.
Imagine that you wanted to be able to create a different themes for you blog type of web application.
You create a few different themes for your blog application. Perhaps you have a light theme and a dark theme.
You end up further sub-classing the blog type of web application so that you have blog-light and blog-dark, all is fine until you realize that you want to be able to have a light and dark theme for a news site—oh, and also for a store application.
Without the bridge pattern, you’d have to follow the same pattern of creating a bunch of different classes for each combination of web application type and theme.
So, you might have store-light and store-dark, and news-site-light and news-site-dark.
This could get out of hand really fast—especially if you decided to introduce a third theme.
Bridge pattern to the rescue.
Any time you have two inheritance hierarchies in your code, you can simplify that code by utilizing this design pattern.
Instead of having specific classes that are a combination of a web application type plus a theme, you make your base web application type utilize a theme interface that can specific implementations of the different kinds of themes.
So, the base web application wouldn’t need to know about the differences between a dark or a light theme, but instead it would utilize a generic, abstract theme interface and you could create a light and a dark implementation of that interface.
Then, your specific implementations of web applications, like blog, new sites and stores could be set to use any of the themes you create, because they access the functionality of the theme through a common theme interface.
Now you can create new kinds of web applications and new kinds of themes and have any combination work together, because you have decoupled the abstraction—the web application—from the implementation—the specific theme.
Back to the Formal Definition
This is the UML (Unified Modeling Language) that represents the formal definition of the bridge pattern. (If you aren’t familiar with UML, don’t worry. UML is just a way of representing relationships between different entities in a software development that uses a standard language and convention. Most UML diagrams can be understood without being an expert in UML.)
Hopefully by this point, it makes a little more sense, but let’s talk about what each part is in our example.
We start with the abstraction. The abstraction is the “web application.” The abstraction would utilize an implementor to perform some kind of function. In our case the “web application” might have a function to draw an input box.
The implementor would be the theme base class or interface. Specific themes would be ConcreteImplementors that would implement some specific functionality, like drawing an input box.
Finally, we would have a RefinedAbstraction, which would be any of the web application types. Those web application types, like blog, storefront, and news site, would utilize the Implementor interface without having to know what ConcreteImplementor was actually providing the implementation.
When to Use the Bridge Pattern
The most simple example I’ve ever come up with for defining when you should use the pattern was actually my answer to a StackOverflow Question asking “When do you use the Bridge Pattern?”
Well, I hope you now have a deep and complete understanding of this pattern, how it works and when to use it.
If you really want to cement your knowledge, the best thing you can do right now is to come up with a few of your own examples and try implementing one or two with some very basic code.
Sometimes learning how to learn is more important than mere learning. Check out my course “10 Steps to Learn Anything Quickly” for a few tips on boosting the rate at which you learn