The Bridge Pattern separates two things: Implementation and Abstraction. Implementation and Abstraction can independently exist and everything is really nicely decoupled. Only on runtime you define to what implementation you "talk" to.

You can imagine this yourself the best with some examples. Consider yourself coding an game. You'll find yourself in an position to use some big library which draws rectangles and circles and different shapes. Maybe you want to write some code for shapes yourself, but others are already implemented in the library you're using.

When you're using SDL you can use the basic 2d render engine to draw some shapes, but you also can use OpenGL. Wouldn't it be nice if you have a possibility to change the API you're using at runtime?

Software Design Pattern - Bridge

For this, the Bridge Pattern is ideal. You define yourself an Interface for your DrawingAPI:

package com.company.bridge;

public interface DrawingApi {
    public String DrawRectangle(float x, float y);
}

Then you have your DrawingAPIs:

package com.company.bridge;

public class DrawingApi1 implements DrawingApi {
    @Override
    public String DrawRectangle(float x, float y) {
        return "API1: Rect";
    }
}

public class DrawingApi2 implements DrawingApi {
    @Override
    public String DrawRectangle(float x, float y) {
        return "API2: Rect";
    }
}

Maybe you'll also need an Adapter for this to work fully. Look at the Adapter Pattern here: https://www.turais.de/software-design-patterns-adapter/

Now you need the "Implementor", in my case it is the Rect (see below). Rect extends a Basic Shape. A Basic Shape looks like this:

abstract public class Shape {
    public Shape(DrawingApi api) {
        this.drawingApi = api;
    }

    abstract String draw();

    DrawingApi drawingApi;
}

The important thing above is the reference to the DrawingApi. And of course my Rect but it is very simple:

public class Rect extends Shape {

    private float x;
    private float y;

    public Rect(float x, float y, DrawingApi api) {
        super(api);
    }

    @Override
    public String draw() {
        return drawingApi.drawRectangle(x, y);
    }
}

If you've defined this so far, you then can change the DrawingAPI even at runtime.

    DrawingApi drawingApi = new DrawingApi1();
    DrawingApi drawingApi2 = new DrawingApi2();

    Rect rect = new Rect(100, 200, drawingApi);
    Rect rect2 = new Rect(100, 200, drawingApi2);
    rect.draw();
    rect2.draw();
    rect = new Rect(200, 200, drawingApi2);
    rect.draw();
  • Bridge and Adapter can look very similar - but with the Bridge you choose to decouple things with the Adapter you have two things you need to couple :)
  • If you look at Abstract Factory - this pattern can be used to create Objects at the Implementation side


Book recommendation

Design Patterns - Elements of Reusable Object-Orientated Software