The Bridge Pattern
The bridge pattern is a great design pattern and an interesting thing about it is that most of the time the need for it doesn’t exist from the beginning and only appears along with new requirements. The initial design may be very good and not implement it, then other requirements appear and, if the proper refactoring isn’t done, the code can easily become a mess.
For example, working on a compiler, we end up with an Assembly base type which represents any assembly. Let’s also assume it has an abstract Compile() method used to generate code.
Going further, we make a distinction between an application (.exe file) and a library (.dll file) but since they are both assemblies and, of course, both can be compiled, we derive them from the base Assembly type and make sure each one implements its own Compile() operation. Now we have the following structure:
As I said, a lot of times the need to implement a bridge pattern arises with new requirements, not being evident from the start. I would say the design is great the way it is. For now. Consider a new requirement of having separate debug and release builds – debug build means embedding some sort of debug information in the compiled assembly, release means optimizing code for best performance.
Having all compilation logic in the Application.Compile and Library.Compile methods and considering there isn’t that much difference between a debug and release build – they do have a lot of common processing, one might be tempted to continue deriving from the already existing classes:
Now DebugApplication is a really small class, its Compile method just adds debug information and calls base.Compile(). Same thing with ReleaseApplication – perform some optimization and call base.Compile(). For the same resons, DebugLibrary and ReleaseLibrary are also two really small classes. We end up with four classes but one might consider they are not hard to maintain since they don’t contain a lot of code themselves, they just override one method. Responsibilities are clear and developers are happy.
Until a new variable is required. Let’s say that after some time, the need for a build targeting Mono instead of .NET arises. For simplicity’s sake, let’s assume that only one kind of build is required for Mono assemblies. We would need to create two additional classes MonoApplication and a MonoLibrary. Then the need for a new type of assembly appears. We would need to create 4 additional classes – the base type and the Debug, Release and Mono children. As you can see, things start to grow exponentially.
The solution is to refactor to the bridge pattern as soon as the first new requirement (Debug and Release builds) makes its appearance. Create a new Compiler base type that handles all compiling behavior. Derive DebugCompiler and ReleaseCompiler from it. Have Assembly keep a reference to a Compiler instance and use that in deriving classes to compile the code. Have the Application.Compile() and Library.Compile() delegate to that compiler instance.
Let’s say that after some time, the need for a build targeting Mono instead of .NET arises. We would need to create a MonoCompiler class. Then the need for a new assembly appears. We would need to derive a new class from Assembly. That’s it.
Notice that going with a bridge from the start, it would’ve been overdesigning – having the following five classes just for compiling an Application and a Library seems a bit too much:
I know the pattern might seem pretty obvious when presented in a simplified example, but a lot of times it’s rather difficult to spot in real-life. The first sign of something going wrong is, as above, when you notice you must create more than one class in the same hierarchy to introduce a single new varying thing.