In software design, the Bridge Pattern is a structural pattern that decouples an abstraction from its implementation so that the two can vary independently. This pattern allows for greater flexibility and scalability in your codebase by separating the high-level logic (abstraction) from the low-level details (implementation).
The Bridge Pattern is particularly useful when you have multiple implementations of an interface, and you want to be able to switch between them without changing the client code that uses these implementations. This separation of concerns makes it easier to maintain and extend your system.
The Bridge Pattern involves two main components:
By using the Bridge Pattern, you create a bridge between these two components, allowing them to work together seamlessly while remaining independent of each other.
Let's walk through an example to illustrate how the Bridge Pattern works. We'll create a simple application that demonstrates different types of drawing tools (abstraction) with different rendering engines (implementor).
First, we define the Implementor interface that will be implemented by different rendering engines.
1interface DrawingImplementor {2drawCircle(x: number, y: number, radius: number): void;3}
Next, we create concrete implementations of the DrawingImplementor interface for different rendering engines.
1class VectorDrawingImplementor implements DrawingImplementor {2drawCircle(x: number, y: number, radius: number): void {3console.log(`Vector: Drawing a circle at (${x}, ${y}) with radius ${radius}`);4}5}67class RasterDrawingImplementor implements DrawingImplementor {8drawCircle(x: number, y: number, radius: number): void {9console.log(`Raster: Drawing a circle at (${x}, ${y}) with radius ${radius}`);10}11}
Now, we define the Abstraction class that will use the DrawingImplementor.
1class Drawing {2protected implementor: DrawingImplementor;34constructor(implementor: DrawingImplementor) {5this.implementor = implementor;6}78drawCircle(x: number, y: number, radius: number): void {9this.implementor.drawCircle(x, y, radius);10}11}
We can create refined abstractions that extend the Drawing class to add more specific behavior.
1class Circle extends Drawing {2constructor(implementor: DrawingImplementor) {3super(implementor);4}56draw(): void {7this.drawCircle(10, 20, 5);8}9}
Finally, we can use the Bridge Pattern to create different drawing tools with different rendering engines.
1const vectorDrawing = new Circle(new VectorDrawingImplementor());2vectorDrawing.draw();34const rasterDrawing = new Circle(new RasterDrawingImplementor());5rasterDrawing.draw();
When you run the above code, you should see the following output:
Vector: Drawing a circle at (10, 20) with radius 5 Raster: Drawing a circle at (10, 20) with radius 5
In this tutorial, we explored the Bridge Pattern and how it can be used to decouple an abstraction from its implementation. In the next section, we will dive into another structural pattern called the Composite Pattern, which allows you to compose objects into tree structures to represent part-whole hierarchies.
Stay tuned for more insights into design patterns and how they can help you build robust and scalable software systems!