Mô hình đại lý (Proxy Pattern) là một mẫu thiết kế cấu trúc cho phép kiểm soát việc truy cập vào một đối tượng thông qua một lớp trung gian. Lớp này đóng vai trò như một "đại diện", có thể thêm hành vi bổ sung — như kiểm tra quyền, ghi log, hoặc quản lý bộ nhớ đệm — mà không làm thay đổi logic cốt lõi của đối tượng gốc.
Phân loại theo cơ chế tạo lớp đại lý
1. Đại lý tĩnh (Static Proxy)
Lớp đại lý được viết sẵn và biên dịch cùng với mã nguồn. Nó phải triển khai cùng giao diện với đối tượng đích để đảm bảo tính tương thích.
- Ưu điểm: Dễ hiểu, kiểm soát rõ ràng từng phương thức, không phụ thuộc vào cơ chế phản xạ.
- Nhược điểm: Không linh hoạt — mỗi đối tượng đích thường cần một lớp đại lý riêng, dẫn đến bloat mã và khó mở rộng.
Dưới đây là ví dụ minh họa với giao diện Service và lớp RealService:
from abc import ABC, abstractmethod
class Service(ABC):
@abstractmethod
def execute(self) -> str:
pass
class RealService(Service):
def execute(self) -> str:
return "Thực thi tác vụ chính"
class LoggingServiceProxy(Service):
def __init__(self, target: Service):
self._target = target
def execute(self) -> str:
print("[LOG] Bắt đầu gọi phương thức...")
result = self._target.execute()
print("[LOG] Hoàn tất gọi phương thức.")
return result
# Sử dụng
core = RealService()
proxy = LoggingServiceProxy(core)
output = proxy.execute() # In ra log trước/sau khi thực thi
2. Đại lý động (Dynamic Proxy)
Lớp đại lý được sinh tự động tại thời điểm chạy bằng cách tận dụng cơ chế __getattr__, __getattribute__, hoặc các thư viện như types.SimpleNamespace hoặc functools.wraps. Đây là cách phổ biến để xây dựng hệ thống cắt ngang (cross-cutting concerns) như AOP.
- Ưu điểm: Tính tái sử dụng cao — một lớp đại lý có thể phục vụ nhiều loại đối tượng khác nhau; dễ tích hợp với framework.
- Nhược điểm: Độ phức tạp tăng do xử lý runtime; chi phí hiệu năng cao hơn so với đại lý tĩnh nếu dùng phản xạ quá mức.
Ví dụ sau sử dụng __getattribute__ để bắt mọi truy cập thuộc tính và phương thức:
class InterceptingProxy:
def __init__(self, wrapped_obj):
object.__setattr__(self, "_wrapped", wrapped_obj)
object.__setattr__(self, "_call_log", [])
def __getattribute__(self, name):
_wrapped = object.__getattribute__(self, "_wrapped")
attr = getattr(_wrapped, name)
if callable(attr):
def intercepted(*args, **kwargs):
self._call_log.append(f"Gọi {name} với {args}, {kwargs}")
result = attr(*args, **kwargs)
self._call_log.append(f"Kết quả {name}: {result}")
return result
return intercepted
return attr
def get_call_history(self):
return self._call_log.copy()
class DataProcessor:
def process(self, data: int) -> int:
return data * 2 + 1
# Khởi tạo và kiểm thử
processor = DataProcessor()
proxy = InterceptingProxy(processor)
proxy.process(5) # Ghi lại toàn bộ hành vi gọi
print(proxy.get_call_history())
Các tình huống ứng dụng điển hình
- Ghi nhật ký và giám sát: Chèn logic ghi thời điểm gọi, tham số đầu vào, thời gian thực thi và giá trị trả về — hỗ trợ phân tích hiệu năng và debug.
- Kiểm soát truy cập: Kiểm tra vai trò người dùng hoặc điều kiện môi trường trước khi chuyển tiếp yêu cầu tới đối tượng đích.
- Bộ nhớ đệm thông minh: Trả về dữ liệu đã lưu trong cache nếu tồn tại, tránh gọi lại hàm tốn kém (ví dụ: truy vấn cơ sở dữ liệu hoặc API bên ngoài).
- Tải chậm (Lazy Loading): Chỉ khởi tạo hoặc tải dữ liệu nặng khi lần đầu tiên thuộc tính đó được truy cập — tối ưu hóa tài nguyên ban đầu.
- Quản lý giao dịch (Transaction Management): Tự động mở/giữ/commit/rollback transaction xung quanh các phương thức thao tác dữ liệu.
- Hỗ trợ RPC và vi dịch vụ: Đại lý đóng vai trò như stub client, đóng gói yêu cầu thành message mạng, gửi tới service remote và giải mã kết quả.
- Tích hợp với AOP: Là nền tảng kỹ thuật để triển khai các aspect như security, metrics, tracing trong các framework hiện đại (ví dụ: Spring AOP, Python’s
aspectlib).