Thiết kế mẫu (design pattern) là cách tiếp cận có hệ thống để giải quyết các vấn đề lập trình phổ biến, giúp cải thiện tính nhất quán và khả năng giao tiếp giữa các lập trình viên. Trong thực tế, hầu hết các mẫu thiết kế không trực tiếp nâng cao hiệu năng, mà chỉ định hình cách tổ chức mã nguồn. Bài viết này tập trung vào bốn mẫu thiết kế liên quan trực tiếp đến hiệu năng: Proxy, Singleton, Flyweight và Prototype.
### Phân tích chậm trễ trong Proxy động
Spring sử dụng Proxy để triển khai AOP. Khi xử lý các cắt mặt phức tạp (ví dụ: xác thực, ghi log), hiệu năng có thể bị ảnh hưởng. Dưới đây là cách dùng arthas để xác định điểm nghẽn hiệu năng mà không cần hiểu sâu mã nguồn:
1. Tạo service đơn giản:
@Component
public class ServiceBean {
public void execute() {
System.out.println("Process completed");
}
}
2. Viết cắt mặt với độ trễ 500ms:
@Aspect
@Component
public class LoggingAspect {
@Pointcut("execution(* com.example.service.ServiceBean.*(..))")
public void servicePointcut() {}
@Before("servicePointcut()")
public void logBefore() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new IllegalStateException();
}
}
}
3. Kiểm tra hiệu năng qua controller:
@Controller
public class PerformanceController {
@Autowired
private ServiceBean service;
@GetMapping("/test")
public String test() {
long start = System.currentTimeMillis();
service.execute();
return "Cost: " + (System.currentTimeMillis() - start) + "ms";
}
}
Chạy endpoint `/test` sẽ hiển thị thời gian thực thi (~500ms). Dùng trace com.example.service.ServiceBean execute trong arthas để xác định điểm chậm trễ nằm ở lớp proxy.
### So sánh Proxy JDK vs CGLib
Trong Java, Proxy động có hai triển khai:
- JDK Proxy: Dựa trên interface, sử dụng
InvocationHandler - CGLib: Chạy trên lớp, dùng
MethodInterceptor
Benchmark hiệu năng (Java 11):
Benchmark Mode Cnt Score Error Units
ProxyBenchmark.cglib thrpt 10 72500.3 ± 1200.5 ops/ms
ProxyBenchmark.jdk thrpt 10 85000.7 ± 900.2 ops/ms
Kết quả cho thấy JDK Proxy nhanh hơn CGLib ~17% trong truy cập phương thức. Spring chọn CGLib chủ yếu vì khả năng proxy lớp không interface.
### Mẫu Singleton: Cách triển khai tối ưu
Spring sử dụng Singleton làm mặc định. Cách triển khai an toàn nhất:
public enum ServiceRegistry {
INSTANCE;
private final ServiceBean service = new ServiceBean();
public ServiceBean getService() {
return service;
}
}
Tránh sử dụng double-checked locking do phức tạp và rủi ro với reordering. Mẫu enum đảm bảo tính nhất quán và không cần khóa đồng bộ.
### Mẫu Flyweight: Tái sử dụng đối tượng
Flyweight tối ưu hiệu năng bằng cách chia sẻ đối tượng. Ví dụ với chiến lược thanh toán:
Map<String, PaymentStrategy> strategies = new HashMap<>();
strategies.put("credit", new CreditPayment());
strategies.put("paypal", new PayPalPayment());
Cách tiếp cận này không chỉ là Flyweight (tái sử dụng) mà cũng là Strategy Pattern (thay đổi chiến lược). Điều quan trọng là xác định ngữ cảnh khi áp dụng mẫu.
### Mẫu Prototype: Giới hạn thực tế
Mẫu Prototype dựa trên phương thức clone() của Object, nhưng thường không được dùng do:
- Chỉ hỗ trợ copy shallow
- Phải viết code phức tạp cho deep copy
- Thay vào đó, sử dụng
newhoặc serial hóa (ví dụ:Serializable) dễ dàng hơn
Trong thực tế, Prototype chủ yếu là một tư duy, không phải giải pháp tốc độ.