The Bridge Pattern

The Bridge Pattern decouples an abstraction from its implementation to enable the two to vary independently. It puts abstraction and implementation in separate class hierarchies. Instead of implementing the abstraction, the “implementation” uses it.

Let’s illustrate it. We want our software to draw maps which are constituted of different shapes (points, lines, polygons, etc.). We could create a Renderer interface with implementations for each shape:

The problem with this design is that we multiply implementations for each shape. To avoid multiplying classes (one of the pattern’s advantages), we can use the bridge pattern:

The bridge is Renderer/Shape

The code would look like:

class Shape {
  private Renderer renderer;
  private int radius;

  public Shape(Renderer renderer, int radius) {
    this.renderer = renderer;
    this.radius = radius;
  }

  public void draw() {
    renderer.drawCircle(radius);
  }
}

// code using the bridge
FlashyRenderer renderer = new FlashyRenderer();
Shape point = new Point(renderer); // which sets the renderer in the class
point.draw();

renderer.updateColor(); // the body of this method may request the shape to re-draw

When to use it?

  • When we multiply classes having the same behavior on similar objects.
  • When abstractions and implementations must be extensible independently.
  • When the implementation must be selected or changed at run-time.
  • When the implementation has no impact on client code.

The Adapter Pattern

When I used to live in France and travel in the US, I had an adapter for my phone charger since the electrical outlets are different: shape, voltage, etc. The Adapter Pattern does the same. It aims to wrap an existing interface to conform it to a required interface.

Let’s consider this company with a CEO, a manager (the “adapter”) and a programmer:

interface CEO { // implementations: bossyCEO, micromanagerCEO, etc. 🙂
  public Demand command();
}

interface Programmer { // implementations: rebelliousProgrammer, etc.
  public void execute(Expectation cmd);
}

interface Manager { // implementations: terrorizedManager, angryManager, etc.
  public void hear(Demand demand);
  public void execute();
}

// adapter class
class SmartManager implements Manager {

  private Programmer programmer; // would not be private if CEO is micromanaging 🙂
  private Project project;

  public SmartManager(Programmer programmer) { // only one programmer 🙂
    this.programmer = programmer;
  }

  public void hear(Demand demand) {
      decrypt(demand);
      discuss(programmer, demand);
      project = plan(demand);
  }

  public void execute() {
    Expectation expectation = project.getPlan();
    programmer.execute(expectation);
  }  
}

The adapter usually wraps the interface it adapts to transform the calls from the consumer interface.

This pattern may require the creation of temporary objects to handle the “adaptation” which impacts performance (time and resources). If it is the case, caching those temporary objects will help.

In our illustration, Project is a temporary object which was created by the plan(Demand demand) method. We could cache project if the CEO has the same demand.

private Map<Demand, Project> plans;

private Project plan(Demand demand) {
  project = plans.get(demand);
  if (project == null) {
     project = ... // create a new project
     plans.add(demand, project); // cache
  }
  return project;
}