JavaRush /Blog Java /Random-VI /Mẫu thiết kế proxy

Mẫu thiết kế proxy

Xuất bản trong nhóm
Trong lập trình, điều quan trọng là phải lập kế hoạch hợp lý cho kiến ​​trúc ứng dụng. Một công cụ không thể thiếu cho việc này là các mẫu thiết kế. Hôm nay chúng ta sẽ nói về Proxy, hay nói cách khác là Phó.

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ó:
  1. Giao diện TimetableTrains.
  2. Lớp TimetableElectricTrainsthực hiện giao diện này.
  3. Thông qua lớp này, mã máy khách tương tác với hệ thống tệp đĩa.
  4. 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ớp TimetableElectricTrains.
Sơ đồ rất đơn giản: Mẫu thiết kế proxy - 2Hiện tại, mỗi khi một phương thức được gọi, printTimetable()lớp TimetableElectricTrainssẽ 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: Mẫu thiết kế proxy - 3Bằng cách này, lớp DisplayTimetablethậm chí sẽ không nhận thấy rằng nó đang tương tác với lớp TimetableElectricTrainsProxychứ 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:
  1. Bộ nhớ đệm.
  2. 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?
  3. Ghi nhật ký yêu cầu.
  4. Kiểm tra dữ liệu và quyền truy cập tạm thời.
  5. Khởi chạy các luồng xử lý song song.
  6. Ghi hoặc đếm lịch sử cuộc gọi.
Có những trường hợp sử dụng khác là tốt. Hiểu được nguyên lý hoạt động của mẫu này, bản thân bạn có thể tìm được ứng dụng thành công cho nó. Thoạt nhìn, Vice thực hiện tương tự như Facade nhưng thực tế không phải vậy. Proxy có giao diện giống với đối tượng dịch vụ . Ngoài ra, đừng nhầm lẫn mẫu này với Decorator hoặc Adaptor . Decorator cung cấp một giao diện mở rộng, trong khi Adaptor cung cấp một giao diện thay thế.

Ư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:
  1. 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.

  2. 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ó.

  3. 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.

  4. 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

  1. Bài viết hay về hoa văn và một chút về “Phó”

Đó là tất cả cho ngày hôm nay! Sẽ thật tuyệt nếu bạn quay lại học và kiểm tra kiến ​​thức mới của mình trong thực tế :)
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION