CodeDifficulty: Intermediate3 min

Bridge Template in Python, Java and TypeScript

Two abstractions and two implementors, combined at construction time.

The Bridge template is the pattern in its pure form, with the participant names from the class diagram and no domain. Each operation reports which abstraction ran and which implementor it called, and the client builds every pair, so the output shows four combinations from four classes. The steps add each participant in Python, and the tabs give the complete program in three languages, each with its output.

Building the template one participant at a time

Implementor

The interface for the low-level side.

py
class Implementor(ABC):
    @abstractmethod
    def operation_impl(self) -> str: ...

Concrete implementors

One class per implementation.

py
class ConcreteImplementorA(Implementor):
    def operation_impl(self) -> str:
        return "ImplementorA"

Abstraction

Holds an implementor, which is the bridge, and calls it.

py
class Abstraction:
    def __init__(self, implementor: Implementor) -> None:
        self._implementor = implementor

Refined abstraction

A variant of the abstraction that uses the same reference.

py
class RefinedAbstraction(Abstraction):
    def operation(self) -> str:
        return f"RefinedAbstraction with {self._implementor.operation_impl()}"

The complete template

py
from abc import ABC, abstractmethod


class Implementor(ABC):
    @abstractmethod
    def operation_impl(self) -> str: ...


class ConcreteImplementorA(Implementor):
    def operation_impl(self) -> str:
        return "ImplementorA"


class ConcreteImplementorB(Implementor):
    def operation_impl(self) -> str:
        return "ImplementorB"


class Abstraction:
    def __init__(self, implementor: Implementor) -> None:
        self._implementor = implementor

    def operation(self) -> str:
        return f"Abstraction with {self._implementor.operation_impl()}"


class RefinedAbstraction(Abstraction):
    def operation(self) -> str:
        return f"RefinedAbstraction with {self._implementor.operation_impl()}"


for abstraction in (Abstraction, RefinedAbstraction):
    for implementor in (ConcreteImplementorA(), ConcreteImplementorB()):
        print(abstraction(implementor).operation())
OutputRan with python:3.12-slim (Python 3.12.13)
Abstraction with ImplementorA
Abstraction with ImplementorB
RefinedAbstraction with ImplementorA
RefinedAbstraction with ImplementorB
The abstraction is joined to its implementor when it is constructed.
java
import java.util.List;
import java.util.function.Function;

public class BrTemplate {
    public static void main(String[] args) {
        List<Function<Implementor, Abstraction>> abstractions =
                List.of(Abstraction::new, RefinedAbstraction::new);
        List<Implementor> implementors =
                List.of(new ConcreteImplementorA(), new ConcreteImplementorB());
        for (Function<Implementor, Abstraction> create : abstractions) {
            for (Implementor implementor : implementors) {
                System.out.println(create.apply(implementor).operation());
            }
        }
    }
}

interface Implementor {
    String operationImpl();
}

class ConcreteImplementorA implements Implementor {
    public String operationImpl() {
        return "ImplementorA";
    }
}

class ConcreteImplementorB implements Implementor {
    public String operationImpl() {
        return "ImplementorB";
    }
}

class Abstraction {
    protected final Implementor implementor;

    Abstraction(Implementor implementor) {
        this.implementor = implementor;
    }

    String operation() {
        return "Abstraction with " + implementor.operationImpl();
    }
}

class RefinedAbstraction extends Abstraction {
    RefinedAbstraction(Implementor implementor) {
        super(implementor);
    }

    @Override
    String operation() {
        return "RefinedAbstraction with " + implementor.operationImpl();
    }
}
OutputRan with eclipse-temurin:21-jdk (Java 21.0.12)
Abstraction with ImplementorA
Abstraction with ImplementorB
RefinedAbstraction with ImplementorA
RefinedAbstraction with ImplementorB
Java places the client first so the source launcher finds `main`; the roles are the same.
ts
interface Implementor {
  operationImpl(): string;
}

class ConcreteImplementorA implements Implementor {
  operationImpl(): string {
    return "ImplementorA";
  }
}

class ConcreteImplementorB implements Implementor {
  operationImpl(): string {
    return "ImplementorB";
  }
}

class Abstraction {
  constructor(protected readonly implementor: Implementor) {}

  operation(): string {
    return `Abstraction with ${this.implementor.operationImpl()}`;
  }
}

class RefinedAbstraction extends Abstraction {
  operation(): string {
    return `RefinedAbstraction with ${this.implementor.operationImpl()}`;
  }
}

for (const create of [Abstraction, RefinedAbstraction]) {
  for (const implementor of [new ConcreteImplementorA(), new ConcreteImplementorB()]) {
    console.log(new create(implementor).operation());
  }
}
OutputRan with node:22-slim (Node 22.23.1), TypeScript 5.9.3
Abstraction with ImplementorA
Abstraction with ImplementorB
RefinedAbstraction with ImplementorA
RefinedAbstraction with ImplementorB
A `protected readonly` constructor parameter declares the reference and assigns it.

Four lines of output come from two abstractions and two implementors, and no class names a pair. A third implementor would make six lines from five classes. The client chooses each pair when it constructs the abstraction, which is also where a real program would pick an implementation from configuration.