Understanding the Bridge Design pattern

Understanding the Bridge Design pattern

ยท

4 min read

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

  1. Abstractions: The abstractions are represented by the MyNewShape class, which defines the common interface for various shapes (MyNewCircle, MyNewSquare, MyNewTriangle). It has the draw() method, which represents the drawing behaviour of each shape. The MyNewShape class is independent of the rendering implementations (Renderer) and does not directly implement the rendering logic.

  2. Implementations: The implementations are represented by the Renderer interface and its concrete implementations PixelRenderer and VectorRenderer. The The Renderer interface defines the common rendering methods (render_circle, render_square, render_triangle). The concrete implementations, PixelRenderer and VectorRenderer, 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.

ย