Tại sao bạn cần một Phó?
Mẫu này giúp giải quyết các vấn đề liên quan đến quyền truy cập được kiểm soát vào một đối tượng. Bạn có thể có câu hỏi: “Tại sao chúng ta cần quyền truy cập có kiểm soát như vậy?” Hãy xem xét một vài tình huống sẽ giúp bạn tìm ra cái gì là gì.ví dụ 1
Hãy tưởng tượng rằng chúng ta có một dự án lớn với một loạt mã cũ, trong đó có một lớp chịu trách nhiệm tải xuống các báo cáo từ cơ sở dữ liệu. Lớp hoạt động đồng bộ, tức là toàn bộ hệ thống ở trạng thái rảnh trong khi cơ sở dữ liệu xử lý yêu cầu. Trung bình, một báo cáo được tạo trong 30 phút. Do tính năng này, quá trình tải lên bắt đầu lúc 00:30 và ban quản lý nhận được báo cáo này vào buổi sáng. Trong quá trình phân tích, hóa ra cần phải nhận báo cáo ngay sau khi nó được tạo, tức là trong vòng một ngày. Không thể sắp xếp lại thời gian khởi chạy vì hệ thống sẽ chờ phản hồi từ cơ sở dữ liệu. Giải pháp là thay đổi nguyên tắc hoạt động bằng cách bắt đầu tải lên và tạo báo cáo trong một luồng riêng biệt. Giải pháp này sẽ cho phép hệ thống hoạt động như bình thường và ban quản lý sẽ nhận được các báo cáo mới. Tuy nhiên, có một vấn đề: mã hiện tại không thể được viết lại vì các chức năng của nó được sử dụng bởi các phần khác của hệ thống. Trong trường hợp này, bạn có thể giới thiệu một lớp proxy trung gian bằng cách sử dụng mẫu Phó, lớp này sẽ nhận được yêu cầu tải lên báo cáo, ghi lại thời gian bắt đầu và khởi chạy một chuỗi riêng biệt. Khi báo cáo được tạo, chuỗi sẽ hoàn thành công việc của mình và mọi người sẽ hài lòng.Ví dụ 2
Nhóm phát triển tạo ra một trang web áp phích. Để có được dữ liệu về các sự kiện mới, họ chuyển sang dịch vụ của bên thứ ba, tương tác với dịch vụ này được thực hiện thông qua một thư viện đóng đặc biệt. Trong quá trình phát triển, một vấn đề đã nảy sinh: hệ thống của bên thứ ba cập nhật dữ liệu mỗi ngày một lần và yêu cầu dữ liệu đó xảy ra mỗi khi người dùng làm mới trang. Điều này tạo ra một số lượng lớn yêu cầu và dịch vụ sẽ ngừng phản hồi. Giải pháp là lưu vào bộ đệm phản hồi của dịch vụ và cung cấp cho khách truy cập kết quả đã lưu trong mỗi lần khởi động lại, cập nhật bộ đệm này nếu cần. Trong trường hợp này, sử dụng mẫu Phó là một giải pháp tuyệt vời mà không làm thay đổi chức năng đã hoàn thiện.Cách thức hoạt động của mẫu
Để triển khai mẫu này, bạn cần tạo một lớp proxy. Nó triển khai một giao diện lớp dịch vụ, mô phỏng hành vi của nó đối với mã máy khách. Do đó, thay vì đối tượng thực, máy khách tương tác với proxy của nó. Thông thường, tất cả các yêu cầu đều được chuyển đến lớp dịch vụ nhưng có thêm các hành động trước hoặc sau lệnh gọi của nó. Nói một cách đơn giản, đối tượng proxy này là một lớp giữa mã máy khách và đối tượng đích. Hãy xem một ví dụ về việc lưu vào bộ nhớ đệm một yêu cầu từ một đĩa cũ rất chậm. Hãy coi nó là lịch trình tàu điện trong một ứng dụng cổ xưa nào đó, nguyên lý hoạt động của nó không thể thay đổi. Đĩa có lịch trình cập nhật được đưa vào mỗi ngày vào một thời điểm cố định. Vì vậy chúng tôi có:- Giao diện
TimetableTrains
. - Lớp
TimetableElectricTrains
thực hiện giao diện này. - Thông qua lớp này, mã máy khách tương tác với hệ thống tệp đĩa.
- Lớp khách hàng
DisplayTimetable
. Phương thức của nóprintTimetable()
sử dụng các phương thức lớpTimetableElectricTrains
.
printTimetable()
lớp TimetableElectricTrains
sẽ truy cập vào đĩa, lấy dữ liệu và cung cấp cho máy khách. Hệ thống này hoạt động tốt nhưng rất chậm. Vì vậy, người ta đã quyết định tăng hiệu suất hệ thống bằng cách thêm cơ chế bộ nhớ đệm. Điều này có thể được thực hiện bằng cách sử dụng mẫu Proxy: Bằng cách này, lớp DisplayTimetable
thậm chí sẽ không nhận thấy rằng nó đang tương tác với lớp TimetableElectricTrainsProxy
chứ không phải với lớp trước đó. Việc triển khai mới tải lịch trình mỗi ngày một lần và theo các yêu cầu lặp lại, sẽ trả về đối tượng đã được tải từ bộ nhớ.
Đối với những nhiệm vụ nào thì sử dụng Proxy tốt hơn?
Dưới đây là một số tình huống mà mẫu này chắc chắn sẽ có ích:- Bộ nhớ đệm.
- Thực hiện lười biếng còn được gọi là thực hiện lười biếng. Tại sao phải tải tất cả một đối tượng cùng một lúc khi bạn có thể tải nó khi cần thiết?
- Ghi nhật ký yêu cầu.
- Kiểm tra dữ liệu và quyền truy cập tạm thời.
- Khởi chạy các luồng xử lý song song.
- Ghi hoặc đếm lịch sử cuộc gọi.
Ưu điểm và nhược điểm
- + Bạn có thể kiểm soát quyền truy cập vào đối tượng dịch vụ theo ý muốn;
- + Khả năng bổ sung để quản lý vòng đời của đối tượng dịch vụ;
- + Hoạt động không có đối tượng phục vụ;
- + Cải thiện hiệu suất và bảo mật mã.
- - Có nguy cơ suy giảm hiệu suất do các biện pháp xử lý bổ sung;
- - Làm phức tạp cấu trúc của các lớp chương trình.
Mẫu thay thế trong thực tế
Hãy cùng bạn triển khai một hệ thống đọc lịch trình tàu từ đĩa:public interface TimetableTrains {
String[] getTimetable();
String getTrainDepartureTime();
}
Một lớp thực hiện giao diện chính:
public class TimetableElectricTrains implements TimetableTrains {
@Override
public String[] getTimetable() {
ArrayList<String> list = new ArrayList<>();
try {
Scanner scanner = new Scanner(new FileReader(new File("/tmp/electric_trains.csv")));
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
list.add(line);
}
} catch (IOException e) {
System.err.println("Error: " + e);
}
return list.toArray(new String[list.size()]);
}
@Override
public String getTrainDepartureTime(String trainId) {
String[] timetable = getTimetable();
for(int i = 0; i<timetable.length; i++) {
if(timetable[i].startsWith(trainId+";")) return timetable[i];
}
return "";
}
}
Mỗi khi bạn cố gắng lấy lịch trình của tất cả các chuyến tàu, chương trình sẽ đọc tệp từ đĩa. Nhưng đây vẫn là những bông hoa. Tệp này cũng được đọc mỗi khi bạn cần lấy lịch trình của một chuyến tàu! Thật tốt khi mã như vậy chỉ tồn tại trong các ví dụ xấu :) Lớp khách hàng:
public class DisplayTimetable {
private TimetableTrains timetableTrains = new TimetableElectricTrains();
public void printTimetable() {
String[] timetable = timetableTrains.getTimetable();
String[] tmpArr;
System.out.println("Поезд\tОткуда\tКуда\t\tВремя отправления\tВремя прибытия\tВремя в пути");
for(int i = 0; i < timetable.length; i++) {
tmpArr = timetable[i].split(";");
System.out.printf("%s\t%s\t%s\t\t%s\t\t\t\t%s\t\t\t%s\n", tmpArr[0], tmpArr[1], tmpArr[2], tmpArr[3], tmpArr[4], tmpArr[5]);
}
}
}
Tệp ví dụ:
9B-6854;Лондон;Прага;13:43;21:15;07:32
BA-1404;Париж;Грац;14:25;21:25;07:00
9B-8710;Прага;Вена;04:48;08:49;04:01;
9B-8122;Прага;Грац;04:48;08:49;04:01
Hãy kiểm tra:
public static void main(String[] args) {
DisplayTimetable displayTimetable = new DisplayTimetable();
displayTimetable.printTimetable();
}
Phần kết luận:
Поезд Откуда Куда Время отправления Время прибытия Время в пути
9B-6854 Лондон Прага 13:43 21:15 07:32
BA-1404 Париж Грац 14:25 21:25 07:00
9B-8710 Прага Вена 04:48 08:49 04:01
9B-8122 Прага Грац 04:48 08:49 04:01
Bây giờ chúng ta hãy thực hiện các bước triển khai mẫu của chúng tôi:
-
Xác định giao diện cho phép bạn sử dụng proxy mới thay vì đối tượng ban đầu. Trong ví dụ của chúng tôi nó là
TimetableTrains
. -
Tạo một lớp proxy. Nó phải chứa một tham chiếu đến một đối tượng dịch vụ (tạo trong một lớp hoặc truyền vào một hàm tạo);
Đây là lớp proxy của chúng tôi:
public class TimetableElectricTrainsProxy implements TimetableTrains { // Ссылка на оригинальный an object private TimetableTrains timetableTrains = new TimetableElectricTrains(); private String[] timetableCache = null @Override public String[] getTimetable() { return timetableTrains.getTimetable(); } @Override public String getTrainDepartureTime(String trainId) { return timetableTrains.getTrainDepartureTime(trainId); } public void clearCache() { timetableTrains = null; } }
Ở giai đoạn này, chúng ta chỉ cần tạo một lớp có tham chiếu đến đối tượng ban đầu và chuyển tất cả các lệnh gọi đến nó.
-
Chúng tôi triển khai logic của lớp proxy. Về cơ bản cuộc gọi luôn được chuyển hướng đến đối tượng ban đầu.
public class TimetableElectricTrainsProxy implements TimetableTrains { // Ссылка на оригинальный an object private TimetableTrains timetableTrains = new TimetableElectricTrains(); private String[] timetableCache = null @Override public String[] getTimetable() { if(timetableCache == null) { timetableCache = timetableTrains.getTimetable(); } return timetableCache; } @Override public String getTrainDepartureTime(String trainId) { if(timetableCache == null) { timetableCache = timetableTrains.getTimetable(); } for(int i = 0; i < timetableCache.length; i++) { if(timetableCache[i].startsWith(trainId+";")) return timetableCache[i]; } return ""; } public void clearCache() { timetableTrains = null; } }
Phương thức này
getTimetable()
kiểm tra xem mảng lịch trình có được lưu trong bộ nhớ hay không. Nếu không, nó sẽ đưa ra yêu cầu tải dữ liệu từ đĩa và lưu trữ kết quả. Nếu yêu cầu đang chạy, nó sẽ nhanh chóng trả về một đối tượng từ bộ nhớ.Nhờ chức năng đơn giản của nó, phương thức getTrainDepartireTime() không cần phải chuyển hướng đến đối tượng ban đầu. Chúng tôi chỉ đơn giản sao chép chức năng của nó thành một phương thức mới.
Bạn không thể làm điều đó. Nếu bạn phải sao chép mã hoặc thực hiện các thao tác tương tự, điều đó có nghĩa là đã xảy ra lỗi và bạn cần nhìn vấn đề từ một góc độ khác. Trong ví dụ đơn giản của chúng tôi, không có cách nào khác, nhưng trong các dự án thực tế, rất có thể mã sẽ được viết chính xác hơn.
-
Thay thế việc tạo đối tượng ban đầu trong mã máy khách bằng một đối tượng thay thế:
public class DisplayTimetable { // Измененная link private TimetableTrains timetableTrains = new TimetableElectricTrainsProxy(); public void printTimetable() { String[] timetable = timetableTrains.getTimetable(); String[] tmpArr; System.out.println("Поезд\tОткуда\tКуда\t\tВремя отправления\tВремя прибытия\tВремя в пути"); for(int i = 0; i<timetable.length; i++) { tmpArr = timetable[i].split(";"); System.out.printf("%s\t%s\t%s\t\t%s\t\t\t\t%s\t\t\t%s\n", tmpArr[0], tmpArr[1], tmpArr[2], tmpArr[3], tmpArr[4], tmpArr[5]); } } }
Bài kiểm tra
Поезд Откуда Куда Время отправления Время прибытия Время в пути 9B-6854 Лондон Прага 13:43 21:15 07:32 BA-1404 Париж Грац 14:25 21:25 07:00 9B-8710 Прага Вена 04:48 08:49 04:01 9B-8122 Прага Грац 04:48 08:49 04:01
Tuyệt vời, nó hoạt động chính xác.
Bạn cũng có thể xem xét một nhà máy sẽ tạo cả đối tượng ban đầu và đối tượng thay thế tùy thuộc vào các điều kiện nhất định.
Liên kết hữu ích thay vì dấu chấm
-
Bài viết hay về hoa văn và một chút về “Phó”
GO TO FULL VERSION