Skip to main content

Bridge Pattern

Definition​

The Bridge decouples an abstraction from its implementation so that the two can vary independently. Instead of a permanent binding between an interface and its implementation, Bridge uses composition to connect them at runtime.

Coffee Shop Example​

Our Coffee Shop offers multiple brewing methods (Espresso, Pour-Over, Cold Brew) that can be prepared on different machines (Basic Machine, Premium Machine, Manual Barista). Instead of creating a class for every combination (EspressoBasicMachine, EspressoPremiumMachine, PourOverBasicMachine...), Bridge separates the two dimensions.

The Problem Without Bridge​

3 methods Γ— 3 machines = 9 classes. Add one more method? Add 3 more classes. This is the Cartesian product explosion.

Structure With Bridge​

3 methods + 3 machines = 6 classes. Any new method or machine adds just 1 class.

Implementation Interface β€” The Machine​

public interface IMachine
{
string GetName();
string HeatWater(int temperature);
string Extract(string coffee);
}

public class BasicMachine : IMachine
{
public string GetName() => "Basic Machine";
public string HeatWater(int temperature) =>
$"Heating water to {temperature}Β°C (standard heating)";
public string Extract(string coffee) =>
$"Extracting {coffee} (standard pressure)";
}

public class PremiumMachine : IMachine
{
public string GetName() => "Premium Machine";
public string HeatWater(int temperature) =>
$"Precision heating to exactly {temperature}Β°C";
public string Extract(string coffee) =>
$"Extracting {coffee} (15 bar pressure with pre-infusion)";
}

public class ManualBarista : IMachine
{
public string GetName() => "Manual Barista";
public string HeatWater(int temperature) =>
$"Gooseneck kettle heated to {temperature}Β°C β€” poured by hand";
public string Extract(string coffee) =>
$"Hand-poured extraction of {coffee} with care";
}

Abstraction β€” The Brewing Method​

public abstract class BrewingMethod
{
protected readonly IMachine Machine;

protected BrewingMethod(IMachine machine)
{
Machine = machine;
}

public abstract string Brew();
}

public class EspressoBrewing : BrewingMethod
{
public EspressoBrewing(IMachine machine) : base(machine) { }

public override string Brew()
{
var heated = Machine.HeatWater(93);
var extracted = Machine.Extract("finely ground espresso");
return $"[{Machine.GetName()}] Espresso:\n {heated}\n {extracted}";
}
}

public class PourOverBrewing : BrewingMethod
{
public PourOverBrewing(IMachine machine) : base(machine) { }

public override string Brew()
{
var heated = Machine.HeatWater(96);
var extracted = Machine.Extract("medium ground coffee");
return $"[{Machine.GetName()}] Pour-Over:\n {heated}\n {extracted}";
}
}

public class ColdBrewBrewing : BrewingMethod
{
public ColdBrewBrewing(IMachine machine) : base(machine) { }

public override string Brew()
{
var extracted = Machine.Extract("coarsely ground coffee (12hr steep)");
return $"[{Machine.GetName()}] Cold Brew:\n Room temp steep\n {extracted}";
}
}

Client β€” The Coffee Shop​

// Combine any brewing method with any machine β€” no new classes needed
var methods = new BrewingMethod[]
{
new EspressoBrewing(new PremiumMachine()),
new PourOverBrewing(new ManualBarista()),
new ColdBrewBrewing(new BasicMachine()),
};

foreach (var method in methods)
{
Console.WriteLine(method.Brew());
Console.WriteLine();
}

// Output:
// [Premium Machine] Espresso:
// Precision heating to exactly 93Β°C
// Extracting finely ground espresso (15 bar pressure with pre-infusion)
//
// [Manual Barista] Pour-Over:
// Gooseneck kettle heated to 96Β°C β€” poured by hand
// Hand-poured extraction of medium ground coffee with care
//
// [Basic Machine] Cold Brew:
// Room temp steep
// Extracting coarsely ground coffee (12hr steep) (standard pressure)
tip

Adding a new brewing method (e.g., AeroPressBrewing) or a new machine (e.g., SmartMachine) adds exactly one class. The two dimensions grow independently.

Bridge vs Adapter​

BridgeAdapter
IntentDesign for independent variation up frontMake existing incompatible interfaces work together
WhenPlanned from the startRetrofitting after the fact
StructureAbstraction ↔ Implementation via compositionClient ↔ Adapter ↔ Adaptee
CouplingLoosely coupled by designLoosely coupled by wrapping

.NET Real-World Usage​

  • IDbConnection / IDbCommand β€” Bridge between ADO.NET abstractions and database-specific implementations
  • ILogger providers β€” the logging abstraction bridges to Console, File, Seq, etc.
  • IConfiguration providers β€” bridges to JSON, environment variables, command line, etc.
  • Stream and its derived types β€” different backing stores (file, memory, network) behind one abstraction

When to Use​

  • You need to avoid a permanent binding between abstraction and implementation
  • Both the abstraction and implementation should be extensible through subclassing
  • Changes to the implementation shouldn't affect client code

When NOT to Use​

  • There's only one implementation β€” no need to decouple
  • The abstraction and implementation are tightly coupled by design and won't vary
  • A simple interface with DI is enough β€” Bridge adds complexity you may not need

Key Takeaways​

  • Bridge prevents the Cartesian product explosion of N Γ— M classes
  • Uses composition to connect abstraction and implementation at runtime
  • Both dimensions can vary independently β€” new methods or machines add one class each
  • Think of Bridge as designing for flexibility from the start, while Adapter fixes incompatibility after the fact

Interview Questions​

Q: How is Bridge different from Strategy? Bridge is a structural pattern focused on separating abstraction from implementation so both can vary. Strategy is a behavioral pattern focused on swapping algorithms at runtime. The structure looks similar, but the intent is different β€” Bridge is about architecture, Strategy is about behavior.

Q: When would you choose Bridge over inheritance? When you have two independent dimensions of variation (brewing method Γ— machine type) and inheritance would create N Γ— M subclasses. Bridge collapses this to N + M.

Q: Does Bridge violate YAGNI? Only if you apply it speculatively. Use Bridge when you know both dimensions will vary. If there's only one implementation, a simple interface suffices.

  • Adapter β€” makes existing interfaces compatible
  • Strategy β€” similar structure, different intent (swapping algorithms)
  • Decorator β€” extends behavior without Bridge's two-hierarchy split
  • SOLID β€” OCP & DIP β€” both principles drive the Bridge's design