skip to content

Walk through the participants of the Bridge pattern and how you wire them together in Java.

level: middleimportance: should knowfreq 40%

answer

  1. Four roles: Abstraction, RefinedAbstraction, Implementor, ConcreteImplementor
  2. Abstraction holds the implementor INTERFACE, not a concrete type
  3. Constructor injection + delegate to primitives
  4. Implementor = low-level primitives the abstraction composes
  5. RemoteControl/Device or Shape/DrawingApi example

basics

~20 s

There are four roles: the abstraction (the high-level interface clients use), refined abstractions (its concrete subclasses), the implementor (the low-level interface), and concrete implementors. The abstraction stores an implementor reference, usually passed in through its constructor, and calls it to do the real work.

solid answer

~40 s

Bridge has four participants. The **Abstraction** is the high-level interface or abstract class the client programs against; it declares operations in client terms and holds a reference to an **Implementor**. **RefinedAbstraction** subclasses extend the abstraction with specific behavior, calling the implementor's primitives. The **Implementor** is a separate interface declaring the low-level primitive operations. **ConcreteImplementor** classes provide actual implementations. Wiring in Java: the implementor is normally passed into the abstraction's constructor (constructor injection) and stored, often `final`; each refined-abstraction method delegates to one or more implementor calls. The client picks both halves and composes them — `new RemoteControl(new Tv())`. The abstraction never knows the concrete implementor class, only the implementor interface, which is what lets you swap implementations freely without touching the abstraction side.

code

java · 44 lines
java
// Implementor (low-level primitives)
interface Device {
    void enable();
    void disable();
    boolean isEnabled();
    void setVolume(int percent);
}

// ConcreteImplementors
class Tv implements Device {
    private boolean on; private int vol;
    public void enable()  { on = true; }
    public void disable() { on = false; }
    public boolean isEnabled() { return on; }
    public void setVolume(int p) { vol = p; }
}
class Radio implements Device { /* ...same contract... */
    private boolean on; private int vol;
    public void enable()  { on = true; }
    public void disable() { on = false; }
    public boolean isEnabled() { return on; }
    public void setVolume(int p) { vol = p; }
}

// Abstraction holds the Implementor INTERFACE (the bridge)
class RemoteControl {
    protected final Device device;                 // constructor injection
    RemoteControl(Device device) { this.device = device; }
    void togglePower() {                            // delegate to primitives
        if (device.isEnabled()) device.disable();
        else device.enable();
    }
}

// RefinedAbstraction
class AdvancedRemote extends RemoteControl {
    AdvancedRemote(Device device) { super(device); }
    void mute() { device.setVolume(0); }
}

// Client composes any remote with any device
RemoteControl r1 = new AdvancedRemote(new Tv());
RemoteControl r2 = new RemoteControl(new Radio());
r1.togglePower();

go deeper

for a junior

Can list the four roles and say the abstraction holds a reference to the implementor that's passed into its constructor.

for a middle

Can wire the full RemoteControl/Device example, use constructor injection storing the interface, and explain the delegation flow.

for a senior

Connects the abstraction-references-interface wiring to the Dependency Inversion Principle and explains why the implementor should expose composable primitives rather than mirror the abstraction.

for a principal

Discusses how to design the implementor interface as a stable port, manage its evolution across many implementors, and decide constructor vs setter injection for testability and runtime swapping.

## The four participants Bridge names four roles. Two live on the 'what' side, two on the 'how' side. 1. **Abstraction** — the high-level type the client uses (e.g. `RemoteControl`). It is usually an abstract class (sometimes an interface) that exposes operations in the client's vocabulary (`togglePower`, `volumeUp`). Crucially, it **holds a reference to an Implementor** and never does the low-level work itself. 2. **RefinedAbstraction** — concrete subclasses of the abstraction that add or specialize behavior (e.g. `AdvancedRemote` adds `mute()`). They build their operations out of implementor primitives. 3. **Implementor** — a *separate* interface declaring the **primitive operations** the abstraction needs (e.g. `Device` with `enable`, `disable`, `setVolume`). This is the 'implementation interface', NOT Java's `implements` keyword. It is intentionally low-level and may look quite different from the abstraction's interface. 4. **ConcreteImplementor** — classes implementing the implementor (e.g. `Tv`, `Radio`). They contain the platform/technology-specific code. ## How the pieces connect The single most important wire is: **the Abstraction holds a reference to the Implementor interface** (never to a concrete implementor). Calls flow Abstraction → Implementor: ``` client → RemoteControl.togglePower() → device.enable()/disable() ``` Because the abstraction depends only on the *interface*, you can hand it any concrete implementor, and you can add new implementors without recompiling the abstraction side. This is the **Dependency Inversion Principle** in action: both high-level (abstraction) and low-level (implementor) code depend on an abstraction (the implementor interface), not on each other's concretions. ## Wiring it in Java The standard technique is **constructor injection**: pass the implementor into the abstraction's constructor and store it in a `final` field. The refined-abstraction methods then **delegate** to that field. A setter variant allows swapping the implementor at runtime, but constructor injection is the common, safer default (immutable reference). ## A complete mental model (RemoteControl / Device) - Abstraction: `RemoteControl` holding a `Device device`. - RefinedAbstraction: `AdvancedRemote extends RemoteControl` adding `mute()`. - Implementor: `Device { void enable(); void disable(); void setVolume(int); boolean isEnabled(); }`. - ConcreteImplementor: `Tv implements Device`, `Radio implements Device`. The client writes `new AdvancedRemote(new Tv())` or `new RemoteControl(new Radio())` — any remote with any device. Add a `SmartSpeaker implements Device` and every remote works with it immediately; add a `VoiceRemote extends RemoteControl` and it works with every device. ## Terms defined - **Constructor injection**: supplying a dependency through the constructor and storing it. - **Delegation**: a method forwarding work to the held collaborator. - **Dependency Inversion Principle (DIP)**: depend on abstractions, not concretions — exactly what the abstraction-holds-implementor-interface wiring achieves. - **Primitive operations**: the small low-level methods on the implementor that the abstraction composes into higher-level behavior. ## Common wiring mistakes - Storing a *concrete* implementor type in the abstraction (defeats swappability — store the interface). - Letting the implementor interface mirror the abstraction's methods one-to-one (it should expose low-level primitives the abstraction *composes*, not a copy of the same API). - Putting business logic in the concrete implementor that belongs in the refined abstraction.

  • Why must the abstraction reference the implementor interface, not a concrete class?
    So any concrete implementor can be plugged in and new ones added without changing the abstraction — it's the Dependency Inversion Principle, and it's what makes the two hierarchies independent.
  • Can the implementor be injected via a setter instead of the constructor?
    Yes; a setter lets you swap the implementation at runtime. Constructor injection is the common default because it gives an immutable, always-set reference.

saying these in an interview costs you the question

  • Storing a concrete implementor type in the abstraction (kills swappability)
  • Making the implementor interface a one-to-one copy of the abstraction's methods
  • Calling 'Implementor' the same thing as Java's implements keyword
  • Forgetting that refined abstractions compose primitives rather than reimplement them

context