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)
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β
| Bridge | Adapter | |
|---|---|---|
| Intent | Design for independent variation up front | Make existing incompatible interfaces work together |
| When | Planned from the start | Retrofitting after the fact |
| Structure | Abstraction β Implementation via composition | Client β Adapter β Adaptee |
| Coupling | Loosely coupled by design | Loosely coupled by wrapping |
.NET Real-World Usageβ
IDbConnection/IDbCommandβ Bridge between ADO.NET abstractions and database-specific implementationsILoggerproviders β the logging abstraction bridges to Console, File, Seq, etc.IConfigurationproviders β bridges to JSON, environment variables, command line, etc.Streamand 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 Γ Mclasses - 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.
Related Topicsβ
- 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