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

Facade Pattern (Mẫu Facade)

Định nghĩa (Definition)

Facade cung cấp interface thống nhất, đơn giản hóa (Unified, Simplified Interface) cho một tập hợp các interface trong subsystem. Nó định nghĩa interface cấp cao hơn khiến subsystem dễ sử dụng hơn bằng cách ẩn độ phức tạp phía sau mặt tiền (Front) sạch sẽ.

Ví dụ Coffee Shop

Đặt một đơn cà phê involves nhiều subsystem: kiểm tra kho (Inventory), pha cà phê (Brewing), tính điểm thân thiết (Loyalty), xử lý thanh toán (Payment), và in biên lai (Receipt). Khách hàng không cần biết về tất cả các bước này. CoffeeShopFacade xử lý orchestration.

Cấu trúc (Structure)

Các thành phần Subsystem

// --- Subsystem: Kho (Inventory) ---
public class InventoryService
{
private readonly Dictionary<string, int> _stock = new()
{
["Espresso"] = 50,
["Latte"] = 30,
["Cappuccino"] = 25,
["Cold Brew"] = 15,
};

public bool CheckAvailability(string coffee) =>
_stock.GetValueOrDefault(coffee, 0) > 0;

public void Deduct(string coffee) =>
_stock[coffee] = _stock[coffee] - 1;
}

// --- Subsystem: Pha chế (Brewing) ---
public record Beverage(string Name, CupSize Size, DateTime BrewedAt);

public class BrewingService
{
public Beverage Brew(string coffee, CupSize size)
{
Console.WriteLine($" Đang pha {size} {coffee}...");
return new Beverage(coffee, size, DateTime.UtcNow);
}
}

// --- Subsystem: Điểm thân thiết (Loyalty) ---
public class LoyaltyService
{
private readonly Dictionary<string, int> _points = new();

public int AddPoints(string customer, decimal amount)
{
var earned = (int)(amount * 10);
_points[customer] = _points.GetValueOrDefault(customer) + earned;
return _points[customer];
}

public int GetPoints(string customer) =>
_points.GetValueOrDefault(customer);
}

// --- Subsystem: Thanh toán (Payment) ---
public record PaymentResult(bool Success, string TransactionId);

public class PaymentService
{
public PaymentResult Charge(string customer, decimal amount)
{
Console.WriteLine($" Đang thu ${amount:F2} từ {customer}...");
return new PaymentResult(true, $"txn_{Guid.NewGuid():N}"[..12]);
}
}

// --- Subsystem: Biên lai (Receipt) ---
public record Order(string Customer, string Coffee, CupSize Size,
decimal Total, string TransactionId, int PointsEarned);

public class ReceiptPrinter
{
public string Print(Order order) =>
$" Biên lai: {order.Size} {order.Coffee} | " +
$"${order.Total:F2} | Txn: {order.TransactionId} | " +
$"+{order.PointsEarned} điểm";
}

Facade — Interface đơn giản hóa (Simplified Interface)

public record OrderResult(bool Success, Order Order, string Message);

public class CoffeeShopFacade
{
private readonly InventoryService _inventory;
private readonly BrewingService _brewing;
private readonly LoyaltyService _loyalty;
private readonly PaymentService _payment;
private readonly ReceiptPrinter _receipt;

private static readonly Dictionary<string, decimal> _prices = new()
{
["Espresso"] = 2.50m,
["Latte"] = 4.00m,
["Cappuccino"] = 3.75m,
["Cold Brew"] = 3.50m,
};

private static readonly Dictionary<CupSize, decimal> _sizeMarkup = new()
{
[CupSize.Small] = 0.0m,
[CupSize.Medium] = 0.50m,
[CupSize.Large] = 1.00m,
};

public CoffeeShopFacade(
InventoryService inventory,
BrewingService brewing,
LoyaltyService loyalty,
PaymentService payment,
ReceiptPrinter receipt)
{
_inventory = inventory;
_brewing = brewing;
_loyalty = loyalty;
_payment = payment;
_receipt = receipt;
}

public OrderResult PlaceOrder(string customer, string coffee, CupSize size)
{
// Bước 1: Kiểm tra kho
if (!_inventory.CheckAvailability(coffee))
return new OrderResult(false, null!, $"Xin lỗi, {coffee} đã hết.");

// Bước 2: Tính giá
var basePrice = _prices[coffee];
var total = basePrice + _sizeMarkup[size];

// Bước 3: Xử lý thanh toán
var payment = _payment.Charge(customer, total);
if (!payment.Success)
return new OrderResult(false, null!, "Thanh toán thất bại.");

// Bước 4: Pha cà phê
var beverage = _brewing.Brew(coffee, size);

// Bước 5: Cộng điểm thân thiết
var points = _loyalty.AddPoints(customer, total);

// Bước 6: Trừ kho
_inventory.Deduct(coffee);

// Bước 7: In biên lai
var order = new Order(customer, coffee, size, total, payment.TransactionId, points);
Console.WriteLine(_receipt.Print(order));

return new OrderResult(true, order, $"Đây là {size} {coffee} của bạn!");
}
}

Client — Đơn giản cực kỳ (Dead Simple)

// Client không biết về subsystem inventory, brewing, loyalty, hay payment
var facade = new CoffeeShopFacade(
new InventoryService(),
new BrewingService(),
new LoyaltyService(),
new PaymentService(),
new ReceiptPrinter());

var result1 = facade.PlaceOrder("Alice", "Latte", CupSize.Large);
Console.WriteLine($" → {result1.Message}\n");

var result2 = facade.PlaceOrder("Bob", "Espresso", CupSize.Small);
Console.WriteLine($" → {result2.Message}\n");

// Output:
// Đang thu $5.00 từ Alice...
// Đang pha Large Latte...
// Biên lai: Large Latte | $5.00 | Txn: txn_a1b2c3d4e5f6 | +50 điểm
// → Đây là Large Latte của bạn!
//
// Đang thu $2.50 từ Bob...
// Đang pha Small Espresso...
// Biên lai: Small Espresso | $2.50 | Txn: txn_g7h8i9j0k1l2 | +25 điểm
// → Đây là Small Espresso của bạn!
mẹo

Client chỉ gọi một method (PlaceOrder) thay vì phối hợp 5 subsystem. Facade không thêm business logic — nó orchestrate các subsystem hiện có.

Facade vs Mediator

FacadeMediator
Mục đíchĐơn giản hóa interface của subsystemTập trung giao tiếp giữa các colleague
HướngMột chiều: client → facade → subsystemĐa chiều: colleague ↔ mediator ↔ colleague
Nhận thứcSubsystem không biết về facadeColleague biết về mediator
CouplingGiảm coupling client-subsystemGiảm coupling colleague-colleague

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

  • IHostBuilder trong .NET — đơn giản hóa host configuration phía sau CreateBuilder()
  • WebApplication.CreateBuilder() — facade cho logging, configuration, DI, Kestrel
  • HttpClient — facade cho HTTP message handler, connection pool, và serialization
  • ORM context (DbContext) — facade cho connection, transaction, change tracking, và querying

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

  • Cần interface đơn giản cho subsystem phức tạp
  • Muốn phân tầng hệ thống — một facade cho mỗi tầng
  • Cần tách biệt client code khỏi chi tiết implementation của subsystem

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

  • Subsystem đã đơn giản — facade thêm indirection không cần thiết
  • Cần kiểm soát chi tiết từng subsystem riêng lẻ — facade ẩn quá nhiều
  • Facade trở thành "god object" — tách thành nhiều facade nếu nó phát triển quá lớn

Điểm chính (Key Takeaways)

  • Facade cung cấp interface đơn giản hóa, thống nhất cho subsystem phức tạp
  • orchestrate subsystem — không chứa business logic
  • Subsystem vẫn hoạt động đầy đủ mà không cần facade — nó là tùy chọn, không bắt buộc
  • Nhiều facade có thể bao cùng một subsystem cho các client khác nhau

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

Q: Facade chỉ là wrapper thôi phải không? Không hẳn. Wrapper (như Adapter) chuyển interface này sang interface khác. Facade đơn giản hóa nhiều interface thành một. Nó không dịch — nó trừu tượng hóa (Abstract) độ phức tạp.

Q: Facade có thể truy cập subsystem trực tiếp hay chỉ qua interface? Cả hai đều hoạt động. Để testability, facade nên phụ thuộc vào abstraction (interface). Trong thực tế, có thể dùng trực tiếp subsystem class nếu chúng ổn định.

Q: Khác biệt giữa Facade và Application Service trong Clean Architecture là gì? Về mặt khái niệm thì tương tự. Application Service (use case) trong Clean Architecture thường đóng vai trò facade — orchestrate domain object và infrastructure. Pattern giống nhau; tên gọi đến từ tầng kiến trúc (Architectural Layer).