Mục Lục
1. Giới thiệu về Proxy Pattern
Trong lĩnh vực phát triển phần mềm, việc quản lý và tối ưu hóa hiệu suất ứng dụng luôn là một bài toán quan trọng. Một trong những giải pháp hiệu quả để giải quyết vấn đề này là sử dụng các Design Pattern, đặc biệt là nhóm Structural Design Pattern. Hôm nay, chúng ta sẽ cùng tìm hiểu về Proxy Pattern, một mẫu thiết kế thuộc nhóm này, và ứng dụng thực tế của nó trong kỹ thuật Lazy Loading. Proxy Pattern tập trung vào việc kiểm soát truy cập đối tượng, mang lại sự linh hoạt và hiệu quả cao hơn cho ứng dụng.
2. Vấn đề và Giải pháp với Proxy Pattern
Trong quá trình phát triển ứng dụng, thường xuyên xuất hiện tình huống cần kiểm soát quyền truy cập vào một đối tượng. Việc này có thể xuất phát từ nhiều lý do, như trì hoãn khởi tạo đối tượng cho đến khi thực sự cần thiết, kiểm tra quyền truy cập trước khi cho phép thao tác, hoặc thậm chí là quản lý đối tượng nằm ở một không gian địa chỉ khác.
Proxy Pattern ra đời để giải quyết vấn đề này. Nó cung cấp một đối tượng “đại diện” (proxy) cho đối tượng thực, cho phép kiểm soát việc truy cập vào đối tượng đó. Proxy có thể thực hiện các tác vụ như:
- Lazy Initialization (Khởi tạo trì hoãn): Tạo đối tượng thực chỉ khi nó thực sự được yêu cầu sử dụng.
- Access Control (Kiểm soát truy cập): Xác thực quyền của người dùng trước khi cho phép truy cập vào đối tượng thực.
- Remote Access (Truy cập từ xa): Cung cấp một giao diện cục bộ cho đối tượng nằm ở một không gian địa chỉ khác (ví dụ: trên một máy chủ khác).
- Logging (Ghi nhật ký): Ghi lại các hoạt động truy cập vào đối tượng thực.
Một số loại Proxy phổ biến:
- Remote Proxy: Đại diện cho một đối tượng nằm ở một không gian địa chỉ khác.
Remote Proxy - Virtual Proxy: Tạo ra một đối tượng tốn kém chỉ khi có yêu cầu.
- Protection Proxy: Kiểm soát quyền truy cập vào đối tượng gốc.
- Smart Reference Proxy: Thay thế con trỏ rỗng và thực hiện các chức năng bổ sung khi đối tượng được truy cập.
3. Sơ Đồ Thiết Kế UML của Proxy Pattern
Để hiểu rõ hơn về cấu trúc của Proxy Pattern, chúng ta có thể xem xét sơ đồ UML sau:
- Subject: Định nghĩa giao diện chung cho cả
RealSubjectvàProxy. Điều này cho phépProxycó thể được sử dụng thay thế choRealSubjectmột cách linh hoạt. - Proxy: Duy trì một tham chiếu đến
RealSubjectđể có thể truy cập nó. Đồng thời,Proxycũng thực hiện giao diện giống nhưRealSubject, cho phép nó đóng vai trò trung gian và kiểm soát việc truy cập.Proxycó thể tạo hoặc xóaRealSubjectkhi cần thiết. - RealSubject: Đây là đối tượng thực mà
Proxyđại diện. Nó chứa logic nghiệp vụ chính và là đối tượng mà chúng ta muốn kiểm soát việc truy cập.
4. So Sánh Proxy Pattern với Các Structural Pattern Khác
Proxy Pattern có những điểm tương đồng với Adapter Pattern và Decorator Pattern, nhưng cũng có những khác biệt quan trọng:
- Adapter Pattern: Cung cấp một giao diện khác cho đối tượng mà nó thích nghi, trong khi Proxy Pattern cung cấp cùng một giao diện với đối tượng gốc.
- Decorator Pattern: Bổ sung thêm các nhiệm vụ cho một đối tượng, trong khi Proxy Pattern kiểm soát truy cập đến một đối tượng. Mặc dù có thể có cài đặt tương tự, mục đích sử dụng của hai pattern này là khác nhau. Protection Proxy có thể được cài đặt giống như một Decorator, nhưng Proxy truy cập đối tượng từ xa sẽ không chứa một tham chiếu trực tiếp đến đối tượng thực sự.
5. Ứng Dụng Lazy Loading của Proxy Pattern
Một trong những ứng dụng phổ biến nhất của Proxy Pattern là Lazy Loading. Lazy Loading là một kỹ thuật cho phép trì hoãn việc tải các đối tượng cho đến khi chúng thực sự cần thiết. Điều này giúp giảm thiểu số lượng tài nguyên cần tải và cải thiện hiệu suất ứng dụng.
Lợi ích của Lazy Loading:
- Giảm thời gian khởi động ứng dụng.
- Tiết kiệm băng thông và tài nguyên hệ thống.
- Cải thiện trải nghiệm người dùng bằng cách tải nhanh hơn các phần quan trọng của ứng dụng.
Ví dụ về Lazy Loading với Realm:
Realm là một cơ sở dữ liệu NoSQL mã nguồn mở, được viết bằng C++, lưu trữ dữ liệu dưới dạng Object. Khi sử dụng Realm, các Object phải kế thừa lớp RealmObject.
public class Person extends RealmObject {
private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
Khi tạo lớp Person, Realm sẽ tự động tạo ra một lớp PersonRealmProxy, lớp này kế thừa lớp Person và chứa một thuộc tính row trỏ đến dữ liệu trong Person:
public class PersonRealmProxy extends Person {
private Row row = ...;
private PersonColumnInfo columnInfo = ...;
@Override
public String getName() {
return row.getString(columnInfo.columnNameIndex);
}
@Override
public void setName(String value) {
row.setString(columnInfo.columnNameIndex, value);
}
}
Khi truy vấn dữ liệu từ Realm, đối tượng trả về là RealmResult thay vì một danh sách các Object. Điều này giúp hạn chế việc khởi tạo tài nguyên không cần thiết:
RealmQuery<Person> query = realm.where(Person.class);
query.beginsWith("name", "Z");
RealmResults<Person> results = query.findAll();
RealmResults thực chất chỉ là một danh sách các index (row) được lấy ra từ B-Tree. Chỉ khi chúng ta thực sự cần một đối tượng Person, đối tượng đó mới được tạo ra:
Person person = results.get(0);
Cải tiến trong phiên bản Realm mới:
Để tăng tính bảo mật và linh hoạt, phiên bản Realm mới sử dụng một interface PersonRealmProxyInterface và một lớp PersonRealmProxy:
public interface PersonRealmProxyInterface {
public String realmGet$name();
public void realmSet$name(String value);
}
public class PersonRealmProxy extends Person implements PersonRealmProxyInterface {
private Row row = ...;
private PersonColumnInfo columnInfo = ...;
public String realmGet$name() {
return row.getString(columnInfo.columnNameIndex);
}
public void realmSet$name(String value) {
row.setString(columnInfo.columnNameIndex, value);
}
}
Interface PersonRealmProxyInterface chứa hai phương thức realmGet$name() và realmSet$name() thay cho việc get/set trực tiếp. Các phương thức này được thực hiện bằng bytecode để bảo mật dữ liệu. Lớp Person sẽ implement interface này và sử dụng các phương thức này để truy cập dữ liệu:
public class Person extends RealmObject implements PersonRealmProxyInterface {
private String name;
public String getName() { return realmGet$name(); }
public void setName(String name) { realmSet$name(name); }
@Override
public String realmGet$name() { return name; }
@Override
public void realmSet$name(String value) { this.name = name; }
}
6. Tổng Kết
Bài viết này đã giới thiệu về Proxy Pattern và một ví dụ điển hình về Virtual Proxy (Lazy Loading). Proxy Pattern có rất nhiều ứng dụng khác trong thực tế, giúp cải thiện hiệu suất và tính linh hoạt của ứng dụng. Việc nắm vững và áp dụng Proxy Pattern một cách hiệu quả sẽ giúp các nhà phát triển xây dựng những ứng dụng mạnh mẽ và dễ bảo trì hơn.
