Chuyển tới nội dung chính

Bridge Pattern (Mẫu Bridge)

Định nghĩa (Definition)

Bridge tách abstraction khỏi implementation để cả hai có thể thay đổi độc lập. Thay vì gắn kết vĩnh viễn giữa interface và implementation, Bridge dùng composition để kết nối chúng lúc runtime.

Ví dụ Coffee Shop

Quán cà phê cung cấp nhiều phương pháp pha chế (Espresso, Pour-Over, Cold Brew) có thể được chuẩn bị trên nhiều loại máy khác nhau (Basic Machine, Premium Machine, Manual Barista). Thay vì tạo class cho mọi tổ hợp (EspressoBasicMachine, EspressoPremiumMachine, PourOverBasicMachine...), Bridge tách hai chiều này.

Vấn đề không có Bridge (The Problem Without Bridge)

3 phương pháp × 3 máy = 9 class. Thêm một phương pháp? Thêm 3 class. Đây là vụ nổ tích Descartes (Cartesian Product Explosion).

Cấu trúc với Bridge (Structure With Bridge)

3 phương pháp + 3 máy = 6 class. Mỗi phương pháp hoặc máy mới chỉ thêm 1 class.

Interface Implementation — Máy pha (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) =>
$"Đun nước đến {temperature}°C (đun tiêu chuẩn)";
public string Extract(string coffee) =>
$"Chiết xuất {coffee} (áp suất tiêu chuẩn)";
}

public class PremiumMachine : IMachine
{
public string GetName() => "Premium Machine";
public string HeatWater(int temperature) =>
$"Đun chính xác {temperature}°C (precision heating)";
public string Extract(string coffee) =>
$"Chiết xuất {coffee} (áp suất 15 bar với pre-infusion)";
}

public class ManualBarista : IMachine
{
public string GetName() => "Manual Barista";
public string HeatWater(int temperature) =>
$"Ống rót cổ ngỗng (Gooseneck) đun đến {temperature}°C — rót bằng tay";
public string Extract(string coffee) =>
$"Chiết xuất {coffee} bằng tay với sự tỉ mỉ";
}

Abstraction — Phương pháp pha (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("cà phê xay mịn (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("cà phê xay trung bình (medium ground)");
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("cà phê xay thô (ngâm 12 giờ)");
return $"[{Machine.GetName()}] Cold Brew:\n Ngâm ở nhiệt độ phòng\n {extracted}";
}
}

Client — Quán cà phê (Coffee Shop)

// Kết hợp bất kỳ phương pháp pha nào với bất kỳ máy nào — không cần class mới
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:
// Đun chính xác 93°C (precision heating)
// Chiết xuất cà phê xay mịn (áp suất 15 bar với pre-infusion)
//
// [Manual Barista] Pour-Over:
// Ống rót cổ ngỗng (Gooseneck) đun đến 96°C — rót bằng tay
// Chiết xuất cà phê xay trung bình (medium ground) bằng tay với sự tỉ mỉ
//
// [Basic Machine] Cold Brew:
// Ngâm ở nhiệt độ phòng
// Chiết xuất cà phê xay thô (ngâm 12 giờ) (áp suất tiêu chuẩn)
mẹo

Thêm phương pháp pha mới (ví dụ: AeroPressBrewing) hoặc máy mới (ví dụ: SmartMachine) chỉ thêm chính xác một class. Hai chiều phát triển độc lập.

Bridge vs Adapter

BridgeAdapter
Mục đíchThiết kế cho sự thay đổi độc lập ngay từ đầuLàm interface không tương thích hoạt động cùng nhau
Khi nàoLên kế hoạch ngay từ đầuSửa chữa sau khi đã có
Cấu trúcAbstraction ↔ Implementation qua compositionClient ↔ Adapter ↔ Adaptee
CouplingLỏng lẻo (Loosely Coupled) theo thiết kếLỏng lẻo bằng cách bao (Wrapping)

Sử dụng thực tế trong .NET (.NET Real-World Usage)

  • IDbConnection / IDbCommand — Bridge giữa ADO.NET abstraction và implementation cụ thể cho từng database
  • ILogger provider — logging abstraction bridge đến Console, File, Seq, v.v.
  • IConfiguration provider — bridge đến JSON, environment variable, command line, v.v.
  • Stream và các derived type — các backing store khác nhau (file, memory, network) phía sau một abstraction

Khi nào sử dụng (When to Use)

  • Cần tránh gắn kết vĩnh viễn giữa abstraction và implementation
  • Cả abstraction và implementation đều cần mở rộng thông qua subclassing
  • Thay đổi implementation không nên ảnh hưởng đến client code

Khi nào KHÔNG sử dụng (When NOT to Use)

  • Chỉ có một implementation — không cần tách biệt
  • Abstraction và implementation gắn kết chặt theo thiết kế và sẽ không thay đổi
  • Interface đơn giản với DI là đủ — Bridge thêm complexity không cần thiết

Điểm chính (Key Takeaways)

  • Bridge ngăn vụ nổ tích Descartes (Cartesian Product Explosion) của N × M class
  • Dùng composition để kết nối abstraction và implementation lúc runtime
  • Cả hai chiều thay đổi độc lập — phương pháp hoặc máy mới chỉ thêm một class
  • Hãy nghĩ Bridge là thiết kế cho linh hoạt ngay từ đầu, còn Adapter sửa không tương thích sau khi đã có

Câu hỏi phỏng vấn (Interview Questions)

Q: Bridge khác Strategy như thế nào? Bridge là structural pattern tập trung vào việc tách abstraction khỏi implementation để cả hai thay đổi. Strategy là behavioral pattern tập trung vào việc hoán đổi thuật toán lúc runtime. Cấu trúc nhìn tương tự, nhưng mục đích khác — Bridge là về kiến trúc, Strategy là về hành vi.

Q: Khi nào chọn Bridge thay vì inheritance? Khi có hai chiều thay đổi độc lập (phương pháp pha × loại máy) và inheritance sẽ tạo N × M subclass. Bridge giảm xuống còn N + M.

Q: Bridge có vi phạm YAGNI không? Chỉ khi áp dụng theo dự đoán. Dùng Bridge khi biết chắc cả hai chiều sẽ thay đổi. Nếu chỉ có một implementation, interface đơn giản là đủ.

  • Adapter — làm interface hiện tại tương thích
  • Strategy — cấu trúc tương tự, mục đích khác (hoán đổi thuật toán)
  • Decorator — mở rộng hành vi không cần Bridge's two-hierarchy split
  • SOLID — OCP & DIP — cả hai nguyên tắc dẫn dắt thiết kế của Bridge