Proxy Pattern (Mẫu Proxy)
Định nghĩa (Definition)
Proxy cung cấp thay thế hoặc placeholder (Substitute or Placeholder) cho object khác để kiểm soát truy cập đến nó. Proxy implement cùng interface với object thật và chuyển tiếp request đến nó, thêm logic kiểm soát trước hoặc sau khi ủy quyền (Delegation).
Ví dụ Coffee Shop
Quán cà phê có CoffeeMachine đắt tiền cần thời gian khởi tạo (làm nóng nồi hơi (Boiler), hiệu chuẩn áp suất). Chúng ta dùng các proxy khác nhau để:
- Virtual Proxy — lazy-load máy đắt tiền chỉ khi cần lần đầu
- Protection Proxy — giới hạn truy cập máy chỉ cho barista được ủy quyền
- Caching Proxy — cache kết quả pha gần đây để tránh pha lại đơn giống hệt
Cấu trúc (Structure)
Subject Interface
public record Beverage(string Coffee, CupSize Size, DateTime BrewedAt);
public interface ICoffeeMachine
{
Beverage Brew(string coffee, CupSize size);
bool IsReady();
}
Real Subject — Khởi tạo tốn kém (Expensive to Create)
public class RealCoffeeMachine : ICoffeeMachine
{
private bool _initialized;
public RealCoffeeMachine()
{
// Khởi tạo tốn kém — mô phỏng 2 giây startup
Console.WriteLine(" [RealCoffeeMachine] Đang làm nóng nồi hơi...");
Console.WriteLine(" [RealCoffeeMachine] Đang hiệu chuẩn áp suất...");
Console.WriteLine(" [RealCoffeeMachine] Máy sẵn sàng!");
_initialized = true;
}
public Beverage Brew(string coffee, CupSize size)
{
Console.WriteLine($" [RealCoffeeMachine] Đang pha {size} {coffee}...");
return new Beverage(coffee, size, DateTime.UtcNow);
}
public bool IsReady() => _initialized;
}
Virtual Proxy — Lazy Loading
public class VirtualProxy : ICoffeeMachine
{
private RealCoffeeMachine? _real;
public Beverage Brew(string coffee, CupSize size)
{
// Chỉ tạo máy thật khi cần lần đầu
_real ??= new RealCoffeeMachine();
return _real.Brew(coffee, size);
}
public bool IsReady() => _real?.IsReady() ?? false;
}
Protection Proxy — Kiểm soát truy cập (Access Control)
public class ProtectionProxy : ICoffeeMachine
{
private readonly ICoffeeMachine _inner;
private readonly string _currentUser;
private readonly HashSet<string> _authorizedUsers;
public ProtectionProxy(ICoffeeMachine inner, string currentUser,
HashSet<string> authorizedUsers)
{
_inner = inner;
_currentUser = currentUser;
_authorizedUsers = authorizedUsers;
}
public Beverage Brew(string coffee, CupSize size)
{
if (!_authorizedUsers.Contains(_currentUser))
throw new UnauthorizedAccessException(
$"{_currentUser} không có quyền sử dụng máy này.");
Console.WriteLine($" [ProtectionProxy] Cấp quyền cho {_currentUser}");
return _inner.Brew(coffee, size);
}
public bool IsReady() => _inner.IsReady();
}
Caching Proxy — Memoization
public class CachingProxy : ICoffeeMachine
{
private readonly ICoffeeMachine _inner;
private readonly Dictionary<string, Beverage> _cache = new();
public CachingProxy(ICoffeeMachine inner)
{
_inner = inner;
}
public Beverage Brew(string coffee, CupSize size)
{
var key = $"{coffee}:{size}";
if (_cache.TryGetValue(key, out var cached))
{
Console.WriteLine($" [CachingProxy] Cache hit cho {key}");
return cached with { BrewedAt = DateTime.UtcNow };
}
Console.WriteLine($" [CachingProxy] Cache miss cho {key}");
var beverage = _inner.Brew(coffee, size);
_cache[key] = beverage;
return beverage;
}
public bool IsReady() => _inner.IsReady();
}
Client — Xếp chồng Proxy (Stacking Proxies)
// Tạo máy khi cần → kiểm tra quyền → cache kết quả
// Client chỉ thấy ICoffeeMachine
var authorizedBaristas = new HashSet<string> { "Alice", "Bob", "Carlos" };
ICoffeeMachine machine = new CachingProxy(
new ProtectionProxy(
new VirtualProxy(),
"Alice",
authorizedBaristas));
Console.WriteLine($"Máy sẵn sàng? {machine.IsReady()}");
// Máy sẵn sàng? False (chưa tạo — lazy!)
Console.WriteLine("\n--- Đơn đầu tiên ---");
var drink1 = machine.Brew("Latte", CupSize.Large);
// [RealCoffeeMachine] Đang làm nóng nồi hơi... ← tạo lần đầu sử dụng
// [RealCoffeeMachine] Đang hiệu chuẩn áp suất...
// [RealCoffeeMachine] Máy sẵn sàng!
// [ProtectionProxy] Cấp quyền cho Alice
// [CachingProxy] Cache miss cho Latte:Large
// [RealCoffeeMachine] Đang pha Large Latte...
Console.WriteLine("\n--- Cùng đơn lần nữa ---");
var drink2 = machine.Brew("Latte", CupSize.Large);
// [CachingProxy] Cache hit cho Latte:Large ← không cần pha lại!
Console.WriteLine("\n--- Người dùng không có quyền ---");
ICoffeeMachine unauthorizedMachine = new ProtectionProxy(
new VirtualProxy(),
"Eve",
authorizedBaristas);
try { unauthorizedMachine.Brew("Espresso", CupSize.Small); }
catch (UnauthorizedAccessException ex) { Console.WriteLine($" BỊ CHẶN: {ex.Message}"); }
// BỊ CHẶN: Eve không có quyền sử dụng máy này.
mẹo
Chú ý việc xếp chồng (Stacking): CachingProxy → ProtectionProxy → VirtualProxy → RealCoffeeMachine. Mỗi proxy thêm một mối quan tâm (Concern). Client thấy ICoffeeMachine và không biết (hoặc không quan tâm) có bao nhiêu proxy trong chuỗi.
Các loại Proxy (Proxy Types)
| Loại | Mục đích | Ví dụ |
|---|---|---|
| Virtual | Khởi tạo lười biếng (Lazy Initialization) | VirtualProxy tạo máy lần đầu sử dụng |
| Protection | Kiểm soát truy cập (Access Control) | ProtectionProxy kiểm tra quyền người dùng |
| Caching | Lưu trữ và tái sử dụng kết quả | CachingProxy tránh pha lại đơn giống hệt |
| Remote | Ẩn giao tiếp mạng (Network Communication) | gRPC client stub, WCF proxy |
| Logging | Ghi lại thao tác (Record Operations) | Ghi mọi lệnh Brew() để audit |
| Smart | Đếm tham chiếu (Reference Counting), locking | Theo dõi usage, tự giải phóng tài nguyên |