I decided to start my little journey trough the GoF patterns with the Bridge pattern, mainly because it’s so rarely used and I wanted to know what it is all about. And after researching it I have to say that I really like it. And although we don’t need often, if we actually do we should better use it or otherwise we will get into a lot of trouble very easily.
By the way, if you do not know what the “GoF patterns” are: These are the design patterns presented in the original work about design patterns written by the “Gang of Four”: Design Patterns: Elements of Reusable Object-Oriented Software. They basically are the most commonly known and most used design patterns.
So what is the Bridge pattern all about and when should we use it? Let me explain it with a little story. There once was a programmer called Timi. Timi would rather be called a software developer but all his colleagues called him a “coder”. One day Timi’s boss wanted him to write a program that implemented an algorithm for thread scheduling. The thread scheduling algorithm Timi was supposed to implement was called Round-robin and his program needed to work specifially for the Windows operating system. Timi started out and created a class called RoundRobinThreadScheduling. Not being aware that evil events were soon to be unfolding, Timi just implemented the algorithm there, using Win32 APIs.
After one day he was done and Timi’s boss met him at the office. He told Timi that requirements have changed. From now on, the company’s software would need to work with Linux as well. Of course Timi – not that stupid of a person at all – immediately realized that even though the program would run on either Windows or Linux, the algorithm itself would still stay the same. Therefore, Timi thought he would just need to change some implementation details to use Linux APIs instead. So he went back to his laptop and he came up with this design:
Sadly though, Timi wasn’t allowed to go into weekend just yet. Timi’s Boss informed him that the customer thought it over and decided that Shortest remaining time first scheduling needed to be implemented as well, for both Windows and Linux. Furthermore Timi’s boss informed him that it was critical that the release date remained the same: Next Monday. And because all the other guys went home just two minutes ago, that meant extra hours for Timi.
So Timi called his family, told them that he wouldn’t make it for dinner. Then, very bravely, he grabbed another coffee and quickly put aside all the thoughts about getting paid too little. He then came up with a design that looked like this (click to enlarge):
Now that was a lot of implementation work. At 10 pm Timi opened up his shell and just at the moment Timi was about to hit the ENTER key for his git rebase -i HEAD~38 he got another call. Of course Timi pretended not to be there anymore and didn’t answer the call. But his boss knew that Timi wasn’t a Git expert and would probably still be there, fighting with all these commits, pulls, pushes, merges, rebases, resets and what not. So Timi’s boss spoke onto the answering machine:
“Hey my friend. Look, I know you are still there. I’ve totally forgotten about telling you that we need one more thread scheduling algorithm. It’s called Fixed-priority pre-emptive scheduling or something like that. I have no idea what that is all about but I’m sure you can figure it out. Also, on the way home our customer called me. He’s like a leaf in the wind I’m tellin’ ya. He said it’s critical that we support Mac OS X, too. Shouldn’t be much of a problem right? Just switch up those APIs and we are good to go. Hey look, if you do all that I’m sure we can talk about raising your salary to 20K right? So have fun, see you next Monday!”
The next week, Timi proudly presented his program to his colleagues. But after looking at Timi’s design they only mocked him, saying that he would never become a true software developer.
But how could poor Timi have done better? Let’s first look at his design. Obviously, it has two very serious problems:
- The amount of classes that must be implemented when a new scheduling algorithm or implementation has to be added is enormous. It is in fact a combinatorial explosion. Adding just one more scheduling algorithm would require 4 new classes to be added.
- Even though subclassing is extensively used, there still is a lot of code duplication. Managing threads on a given OS should be independent of the scheduling algorithm after all. One could say that subclassing allowed Timi to avoid code duplication in only one dimension. He choose to not duplicate the scheduling algorithms, but he had to duplicate thread implementation.
What would a design look like which avoided the above problems? This answer is provided by the Bridge pattern.
In the Bridge pattern, the two dimensions (scheduling algorithms and thread implementations in our example) are generally called abstraction and implementation. It uses class composition to “bridge” the gap between the two dimensions, hence the name Bridge pattern. And this is how Timi’s program would look like after refactoring it accordingly (click to enlarge):
Now isn’t that beeauutiful? I really have to say that I think this is truly beautiful. Just by looking at the refactored class diagram … everything is so much more structured and compact. But the real beauty comes if you actually wire that together:
- You can mix every concrete abstraction with every concrete implementation. Just look at it and tell me what you want. Your order please? Oh you like Shortest remaining time first scheduling on a Mac? No problem:
new SrtfThreadScheduling(new MacThreadAccess())
- After adding a new concrete abstraction, you can immediately use it with every concrete implementation. You could add the FIFO scheduling algorithm and immediately use it on Windows, Linux and Mac. It’s just there for you.
- After adding a new concrete implementation, you can immediately use it with every concrete abstraction. You could add support for the Solaris operating system and immediately use every scheduling algorithm on Solaris.
- The design suggested by the bridge pattern allows developers to parallelize work. One person could work on the scheduling algorithms, while another could work on thread access implementations. This is especially important in agile projects with short development cycles where you want as many developers as possible to work simultaneously on a highly prioritized feature.
- Finally, this design incorporates the object-oriented design principles Single Responsibility and Separation Of Concerns very nicely. This means that every class is focused on just one thing. Therefore, everything is loosely coupled and maintenance becomes a lot easier (and cheaper).
Here is a more generalized diagram of the pattern:
(Image source: http://www.lepus.org.uk/ref/companion/Bridge.xml)
This diagram is based on LePUS3. LePUS3 is an object-oriented design description language. The reason why I favor it above a class diagram is because it allows to express complex designs in compact charts using only few symbols. If you cannot read the above diagram, checkout http://www.lepus.org.uk/ref/legend/legend.xml.
As I was thinking about a proper example for the bridge pattern, one thing came into my mind almost immediately: Lego. We all know Lego from our childhoods. Although you could buy just stones without any instructions, most of the time you got a nice little blueprint which told you how to assemble the individual pieces. So we had a (relatively small) number of different pieces and a huge set of (available) blueprints. Furthermore, the different pieces came in different flavors like Lego Technic, Lego Duplo and so on.
In my example application, the abstraction is the blueprint. Using a blueprint you can then assemble an image using “blocks”. To display the image you can choose from different output media (the implementation).
There are two blueprints (concrete abstractions):
There are two output media (concrete implementations):
The respective class diagram looks as following (click to enlarge):
The application expects two program arguments upon launch: blueprint and output medium. There are four possible configurations:
1) Checkerboard printed to console:
2) Checkerboard displayed in Swing application window:
3) House printed to console:
4) House displayed in Swing application window:
Upon launch, the desired blueprint and output medium are instantiated and the output medium is given to the blueprint. The blueprint tells the output medium how to assemble the blocks and the output medium’s job is to properly display the blocks on screen.
I want to keep this series of blog posts focused about design patterns, so I won’t explain the Scala source code here. This would require a separate series of blog posts. However, I really want to encourage you to take a look at the sources. They are heavily augmented with comments that explain concepts of the language as well as the differences to Java. If you are interested, you can download the Eclipse project here:
If you can live without syntax highlighting, you can simply open the source files with a text editor just fine. Alternatively, you can get the official Scala IDE (Eclipse plug-in) from here:
Real World Bridge
According to my plan, the final step during my visit of a particular design pattern is to analyze existing source code in productive systems in the hope of finding existing or possible applications of the pattern in question. So I did exactly that with the code base of the product we develop at the company I’m working for.
I really tried very hard for several hours to find an application of the bridge pattern within our code base but to no avail. I would say that I know the code base very well so I would argue that so far, we have not used the pattern at all.
Next, I tried to find places where the bridge pattern would have been a good design choice, all in the hope of being able to refactor the code in question accordingly. I had some places in mind which I thought would benefit from the bridge pattern but after analyzing these places in detail I decided against it.
The bridge pattern requires a very specific type of problem. When searching for appropriate places to use it, we must first of all identify what would be the abstraction and what would be the implementation. But the additional requirement is that both abstraction and implementation must need to vary independently from each other. Furthermore, there should be at least two concrete abstractions and two concrete implementations. Otherwise, either the abstraction or the implementation didn’t need to vary thus far. But then, the whole point of the bridge pattern is to solve the problem of two varying dimensions.
What I did find was a place that might make the usage of a bridge necessary in the future. However, the chances for the requirements to change into that direction are very small as far as I can tell.
The code in question is responsible for providing an export of data from our product into another system. The abstraction would define the logic about how data gets exported, the implementation would be the export into a concrete system. So far, we have two concrete implementations:
- Export to the third-party system
- Export to console (to test the export without connecting to the third-party system)
We do not have multiple ways about how we export the data though. Therefore, a bridge is not required. The class diagram as it is now looks like this:
If you have any bridges in your code, or if you stumble over a problem that you are able to solve with the bridge pattern in the future, please let me know about it in the comments. I would really like to see a “real world bridge”.
We have seen that the bridge pattern enables us to decouple an abstraction from it’s implementation and how that could have saved poor Timi from a lot of work and the mockery of his colleagues.
A bridge allows us to avoid code duplication if we need to generalize two dimensions (also known as nested generalizations), using two class hierarchies connected by an association from abstraction to implementation (the bridge).
Compared to patterns like Singleton, Adapter or Factory, the Bridge pattern is only used on rare occasions because the flexibility it provides is not needed most of the time. A good indicator that you might need a bridge could be if you happen to think “for this problem, it would be nice to have multiple inheritance”.
Please share your opinions about this blog post, the Bridge pattern, or the Scala example by leaving a comment. Constructive criticism is always welcome :-) And if you already had the pleasure to use the bridge pattern, please let me know as well.