Understanding the Bridge Design pattern
Building the problem
Suppose you have an abstraction class Shape and a concrete class Circle
We want to add rendering functionality to our shapes
Also, this rendering can be of two types, a pixel rendering or a vector rendering, so corresponding to our old Circle subclass we would need two subclasses to cover concrete implementations of this new functionality
Now we have decided to introduce a new shape implementation- a Square, so we would now have to add pixel and vector rendering squares
we further introduce a new shape a Triangle...
Now if we introduce a third type of rendering ie type '3'
One class needs to be added for EVERY SHAPE ie circle, square and triangle
ie we need to create classes MyNewCircle3, MyNewSquare3, MyNewTriangle3
and we can see the problem now 🥲
this is called a state space explosion
our classes are increasing as N * M...
This is happening as we are trying to extend shape abstraction in two independent dimensions.
Now let's go back in time with our example and see how we could have done things differently ...
We want to add rendering functionality to our shapes,
instead of
do
Above we can see how our Concrete shape will take a Concrete renderer and achieve the desired functionality through composition instead of inheritance.
This prevents state-space-explosion of classes and further improves loose coupling.
Now if we decided to add a new shape only one additional class would be added instead of two as shown in the earlier example. Let's add two more shapes as we did in the earlier example, adding two additional subclasses instead of four(in the earlier case) to implement the same functionality
we have implemented the same functionality but by using composition+ inheritance instead of just inheritance.
Now if we were to add a type 3 rendering, earlier we needed to add a subclass for each shape but now we just need to add a renderer namely Type3Renderr and implement the methods for every shape,
The Bridge pattern is a structural design pattern that decouples an abstraction from its implementation, allowing it to vary independently and also prevents state-space-explosion
Abstractions: The abstractions are represented by the
MyNewShape
class, which defines the common interface for various shapes (MyNewCircle
,MyNewSquare
,MyNewTriangle
). It has thedraw()
method, which represents the drawing behaviour of each shape. TheMyNewShape
class is independent of the rendering implementations (Renderer
) and does not directly implement the rendering logic.Implementations: The implementations are represented by the
Renderer
interface and its concrete implementationsPixelRenderer
andVectorRenderer
. The TheRenderer
interface defines the common rendering methods (render_circle
,render_square
,render_triangle
). The concrete implementations,PixelRenderer
andVectorRenderer
, provide specific rendering behaviours for the shapes, such as pixel-based rendering or vector-based rendering.
Our new class has a BRIDGE to the Renderer implementation
Every concrete shape has a reference to a bridge, this renderer can be a vector or pixel renderer both of which have the render_circle method
Other Manifestations of Bridge Design Pattern
A PIMPL (Pointer to Implementation) idiom is a design pattern used in C++ to hide the implementation details of a class from its users. It is also known as the Compilation Firewall idiom. The primary goal of using PIMPL is to reduce compile-time dependencies and provide a stable binary interface for the class, making it easier to maintain and evolve the codebase without affecting client code.
In a regular C++ library, the function implementations (definitions) are placed in separate cpp files which are not shipped to the client. These cpp files are compiled into object files, and only the declarations (function signatures, class definitions, etc.) are present in the header files. When clients use the library, they include the appropriate header files in their code and link against the precompiled object files during the linking phase. This avoids code duplication and reduces executable size and compilation times. Needless to say, now internal implementations (cpp) can be changed without modifying the abstractions (hpp)
The client doesn’t even have to re-compile the code - it improved compilation speeds.
Subscribe to my newsletter
Read articles from Paras kaushik directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Paras kaushik
Paras kaushik
Hello, I'm Paras Kaushik! 👋 I'm a dedicated software engineer based in India, specializing in C++ and proficient in the MERN stack. 🤝 Interested in collaborating on innovative projects that require my technical expertise. 💬 Passionate about participating in discussions related to software architecture and best practices. 📧 Feel free to reach out to me via email: [paraskaushik12@gmail.com] 🔗 Connect with me on LinkedIn: [https://www.linkedin.com/in/the-paras-kaushik/]