Skip to content

Bridge

Also known as

  • Handle/Body

Intent

Decouple an abstraction from its implementation so that the two can vary independently.

Explanation

Real-world example

Consider you have a weapon with different enchantments, and you are supposed to allow mixing different weapons with different enchantments. Would you create multiple copies of each weapon for each enchantment, or create separate enchantments and set them for the weapon as needed? Bridge pattern allows you to do the second.

In plain words

Bridge pattern is about favoring composition over inheritance. Implementation details are pushed from a hierarchy to another object with a separate hierarchy.

Wikipedia says

The bridge pattern is a design pattern used in software engineering that is meant to "decouple an abstraction from its implementation so that the two can vary independently."

sequenceDiagram
    participant Client
    participant Sword
    participant SoulEatingEnchantment

    Client->>Sword: wield()
    Sword->>SoulEatingEnchantment: onActivate()
    SoulEatingEnchantment-->>Sword: done
    Client->>Sword: swing()
    Sword->>SoulEatingEnchantment: apply()
    SoulEatingEnchantment-->>Sword: done
    Client->>Sword: unwield()
    Sword->>SoulEatingEnchantment: onDeactivate()
    SoulEatingEnchantment-->>Sword: done

Programmatic Example

Translating our weapon example from above. Here we have the Weapon hierarchy:

internal interface Weapon {
    val enchantment: Enchantment
    fun wield()
    fun swing()
    fun unwield()
}

internal class Sword(
    override val enchantment: Enchantment,
) : Weapon {
    override fun wield() {
        logger.info("The sword is wielded.")
        enchantment.onActivate()
    }

    override fun swing() {
        logger.info("The sword is swung.")
        enchantment.apply()
    }

    override fun unwield() {
        logger.info("The sword is unwielded.")
        enchantment.onDeactivate()
    }
}

Here's the separate enchantment hierarchy:

internal interface Enchantment {
    fun onActivate()
    fun apply()
    fun onDeactivate()
}

internal class FlyingEnchantment : Enchantment {
    override fun onActivate() {
        logger.info("The item begins to glow faintly.")
    }

    override fun apply() {
        logger.info(
            "The item flies and strikes the enemies"
                + " finally returning to owner's hand."
        )
    }

    override fun onDeactivate() {
        logger.info("The item's glow fades.")
    }
}

internal class SoulEatingEnchantment : Enchantment {
    override fun onActivate() {
        logger.info("The item spreads bloodlust.")
    }

    override fun apply() {
        logger.info(
            "The item eats the soul of enemies."
        )
    }

    override fun onDeactivate() {
        logger.info("Bloodlust slowly disappears.")
    }
}

Here are both hierarchies in action:

logger.info("The knight receives an enchanted sword.")
val enchantedSword = Sword(SoulEatingEnchantment())
enchantedSword.wield()
enchantedSword.swing()
enchantedSword.unwield()

logger.info("The valkyrie receives an enchanted hammer.")
val hammer = Hammer(FlyingEnchantment())
hammer.wield()
hammer.swing()
hammer.unwield()

Program output:

The knight receives an enchanted sword.
The sword is wielded.
The item spreads bloodlust.
The sword is swung.
The item eats the soul of enemies.
The sword is unwielded.
Bloodlust slowly disappears.
The valkyrie receives an enchanted hammer.
The hammer is wielded.
The item begins to glow faintly.
The hammer is swung.
The item flies and strikes the enemies finally returning to owner's hand.
The hammer is unwielded.
The item's glow fades.

Class diagram

classDiagram
    class Enchantment {
        <<interface>>
        +apply()
        +onActivate()
        +onDeactivate()
    }
    class FlyingEnchantment {
        +apply()
        +onActivate()
        +onDeactivate()
    }
    class SoulEatingEnchantment {
        +apply()
        +onActivate()
        +onDeactivate()
    }
    class Weapon {
        <<interface>>
        +enchantment Enchantment
        +swing()
        +unwield()
        +wield()
    }
    class Sword {
        +enchantment Enchantment
        +swing()
        +unwield()
        +wield()
    }
    class Hammer {
        +enchantment Enchantment
        +swing()
        +unwield()
        +wield()
    }
    Sword --> Enchantment : enchantment
    Hammer --> Enchantment : enchantment
    FlyingEnchantment ..|> Enchantment
    Hammer ..|> Weapon
    SoulEatingEnchantment ..|> Enchantment
    Sword ..|> Weapon

Applicability

Use the Bridge pattern when:

  • You want to avoid a permanent binding between an abstraction and its implementation, for example when the implementation must be selected at run-time.
  • Both the abstractions and their implementations should be extensible by subclassing independently.
  • Changes in the implementation of an abstraction should have no impact on clients.
  • You have a proliferation of classes indicating the need for splitting an object into two parts.

Consequences

Benefits:

  • Decouples interface and implementation, allowing them to vary independently.
  • Improves extensibility — you can extend abstraction and implementation hierarchies independently.
  • Hides implementation details from clients.

Trade-offs:

  • Increases complexity due to the extra layer of indirection.
  • Can complicate the system architecture for clients unfamiliar with the pattern.
  • Adapter: Adapter changes an interface after design; Bridge designs the abstraction and implementation to vary independently from the start.
  • Strategy: Similar structure but different intent — Strategy changes behavior, Bridge separates abstraction from implementation.

Credits