Hi! We now continue to delve into an extensive and super important useful topic: design patterns. Introducing the bridge design pattern - 1 Today let's talk about the bridge pattern. Like other patterns, the bridge pattern serves to solve typical problems that a developer encounters when designing software architecture. Today let's study the features of this pattern and find out how to use it.

What is the bridge pattern?

The bridge pattern is a structural design pattern. In other words, its main job is to create a full-fledged structure out of classes and objects. A bridge does this by dividing one or more classes into separate hierarchies: abstraction and implementation. A change in functionality in one hierarchy does not entail a change in the other. That's all fine and good, but this definition is very broad and doesn't answer the most important question: "What is the bridge pattern?" I think it will be easier for you to understand its practical application. So right off, let's create a classic scenario for the bridge pattern. We have an abstract Shape class, which represents a generic geometric figure:
  • Shape.java

    public abstract class Shape {
       public abstract void draw();
    }

    When we decide to add shapes like triangles and rectangles, we will make them inherit the Shape class:

  • Rectangle.java:

    public class Rectangle extends Shape {
       @Override
       public void draw() {
           System.out.println("Drawing rectangle");
       }
    }
  • Triangle.java:

    public class Triangle extends Shape {
       @Override
       public void draw() {
           System.out.println("Drawing triangle");
       }
    }
Everything looks simple until the moment we introduce the concept of color. That is, each shape will have its own color, and the functionality of the draw() method will depend on this color. To have different implementations of the draw() method, then we need to create a class for each shape-color combination. If we have three colors, then we need six classes: TriangleBlack, TriangleGreen, TriangleRed, RectangleBlack, RectangleGreen and RectangleRed. Six classes isn't such a big problem. But! If we need to add a new shape or color, then the number of classes grow exponentially. How to get out of this situation? Storing color in a field and enumerating all the options using conditional statements is not the best solution. A good solution is to move color to a separate interface. No sooner said than done: let's create a Color interface with three implementations: BlackColor, GreenColor and RedColor:
  • Color.java:

    public interface Color {
       void fillColor();
    }
  • BlackColor.java:

    public class BlackColor implements Color {
       @Override
       public void fillColor() {
           System.out.println("Filling in black color");
       }
    }
  • GreenColor.java

    public class GreenColor implements Color {
       @Override
       public void fillColor() {
           System.out.println("Filling in green color");
       }
    }
  • RedColor.java

    public class RedColor implements Color {
       @Override
       public void fillColor() {
           System.out.println("Filling in red color");
       }
    }

    Now we add a Color field to the Shape class. We'll get its value in the constructor.

  • Shape.java:

    public abstract class Shape {
       protected Color color;
    
       public Shape(Color color) {
           this.color = color;
       }
    
       public abstract void draw();
    }

    We will use the color variable in Shape implementations. This means that shapes can now use the functionality of the Color interface.

  • Rectangle.java

    public class Rectangle extends Shape {
    
       public Rectangle(Color color) {
           super(color);
       }
    
       @Override
       public void draw() {
           System.out.println("Drawing rectangle");
           color.fillColor();
       }
    }
Ta-da! Now we can create different colors and shapes ad infinitum, and the number of classes will increase only linearly. The Color color field is a bridge that connects two separate class hierarchies.

How to build a bridge: abstraction and implementation

Let's look at a class diagram that depicts the bridge pattern: Introducing the bridge design pattern - 2Here you can see two independent structures that can be modified without affecting each other's functionality. In our case:
  • Abstraction is the Shape class
  • RefinedAbstraction is the Triangle and Rectangle classes
  • Implementor is the Color interface
  • ConcreteImplementor is the BlackColor, GreenColor and RedColor classes.
The Shape class is an abstraction — a mechanism for managing the filling of shapes with various colors, which delegates to the Color interface (Implementor). The Triangle and Rectangle classes are concrete classes that use the mechanism made available by the Shape class. BlackColor, GreenColor and RedColor are concrete implementations in the Abstraction hierarchy. Introducing the bridge design pattern - 3

Where to use the bridge pattern

A huge benefit of using this pattern is that you can make changes to the functional classes in one hierarchy without breaking the logic of the other. Also, this approach helps to reduce coupling between classes. The main requirement when using this pattern is "follow the instructions" — don't ignore any of them! To that end, let's figure out the situations when you should definitely use the bridge pattern:
  1. If you need to expand the number of entities based on combinations of two concepts (e.g. shapes and colors).

  2. If you want to divide a large class that does not meet the single-responsibility principle into smaller classes that have narrow functionality.

  3. If it is necessary to make changes to the logic of certain entities while the program is running.

  4. If it is necessary to hide an implementation from the clients of the class or library.

When you use this this pattern, always remember that it adds additional entities to your code — it might not make sense to use it in a project where there is only one shape and one or two possible colors.

Pros and cons of the pattern

Like other patterns, a bridge has both advantages and disadvantages. Advantages of the bridge pattern:
  1. It improves the scalability of code — you can add functionality without fear of breaking something in another part of the program.
  2. It reduces the number of subclasses when the number of entities would otherwise be based on combinations of two concepts (for example, shapes and colors).
  3. It makes it possible to separately work on two separate hierarchies — Abstraction and Implementation. Two different developers can make changes without delving into the details of each other's code.
  4. It reduces the coupling between classes — the only place where the two classes are coupled is the bridge (i.e. the Color color field).
Disadvantages of the bridge pattern:
  1. Depending on the specific situation and the overall structure of a project, it could negatively impact a program's performance (for example, if you need to initialize more objects).
  2. It makes code less readable due to the need to switch between the two classes.

Difference from the strategy pattern

The bridge pattern is often confused with another design pattern — strategy. They both use composition, delegating work to other objects. But there is a difference between them, and it is huge. The strategy pattern is a behavioral pattern: it solves completely different problems. Strategy allows algorithms to be interchanged, while bridge separates an abstraction from implementations in order to choose between different implementations. In other words, unlike a strategy, a bridge applies to entire entities or hierarchical structures. The bridge pattern can be a good weapon in a developer's arsenal. The main thing is to identify the situations where it is worth using it and recognize when some other pattern is appropriate. If you are not familiar with other patterns, read the following articles: