CodeGym /Blog Java /Ngẫu nhiên /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à lập kế hoạch kiến ​​trúc ứng dụng của bạn một cách chính xác. Các mẫu thiết kế là một cách không thể thiếu để thực hiện điều này. Hôm nay chúng ta hãy nói về proxy.

Tại sao bạn cần một proxy?

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ể hỏi, "Tại sao chúng ta cần truy cập có kiểm soát?" Hãy xem xét một vài tình huống sẽ giúp bạn tìm ra những 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 xuất báo cáo từ cơ sở dữ liệu. Lớp học hoạt động đồng bộ. Tức là toàn bộ hệ thống không hoạt động trong khi cơ sở dữ liệu xử lý yêu cầu. Trung bình, mất 30 phút để tạo một báo cáo. Theo đó, quy trình xuất bắt đầu từ 12h30 và ban lãnh đạo nhận báo cáo vào buổi sáng. Một cuộc kiểm toán cho thấy rằng sẽ tốt hơn nếu có thể nhận được báo cáo ngay lập tức trong giờ làm việc bình thường. Thời gian bắt đầu không thể hoãn lại và hệ thống không thể chặn trong khi chờ phản hồi từ cơ sở dữ liệu. Giải pháp là thay đổi cách hệ thống hoạt động, tạo và xuất báo cáo trên một chuỗi 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 đề: không thể viết lại mã hiện tại, vì các phần khác của hệ thống sử dụng chức năng của nó. Trong trường hợp này, chúng tôi có thể sử dụng mẫu proxy để giới thiệu một lớp proxy trung gian sẽ nhận yêu cầu xuất 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 kết thúc và mọi người đều hài lòng.

ví dụ 2

Một nhóm phát triển đang tạo một trang web sự kiện. Để có được dữ liệu về các sự kiện mới, nhóm truy vấn dịch vụ của bên thứ ba. Một thư viện tư nhân đặc biệt tạo điều kiện tương tác với dịch vụ. Trong quá trình phát triển, một vấn đề được phát hiện: hệ thống của bên thứ ba cập nhật dữ liệu của nó mỗi ngày một lần, nhưng một yêu cầu được gửi tới nó mỗi khi người dùng làm mới một trang. Điều này tạo ra một số lượng lớn yêu cầu và dịch vụ ngừng phản hồi. Giải pháp là lưu phản hồi của dịch vụ vào bộ nhớ cache và trả lại kết quả đã lưu trong bộ nhớ cache cho khách truy cập khi các trang được tải lại, cập nhật bộ nhớ cache khi cần. Trong trường hợp này, mẫu thiết kế proxy là một giải pháp tuyệt vời không làm thay đổi chức năng hiện có.

Nguyên tắc đằng sau mẫu thiết kế

Để triển khai mẫu này, bạn cần tạo một lớp proxy. Nó thực hiện giao diện của lớp dịch vụ, bắt chước hành vi của nó đối với mã máy khách. Theo cách này, máy khách tương tác với một proxy thay vì đối tượng thực. Theo quy định, tất cả các yêu cầu được chuyển đến lớp dịch vụ, nhưng với các hành động bổ sung trước hoặc sau. Nói một cách đơn giản, proxy là một lớp giữa mã máy khách và đối tượng đích. Hãy xem xét ví dụ về kết quả truy vấn lưu vào bộ nhớ đệm từ một đĩa cứng cũ và rất chậm. Giả sử chúng ta đang nói về lịch trình tàu điện trong một ứng dụng cổ xưa nào đó mà logic của nó không thể thay đổi được. Một đĩa có thời gian biểu 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. TrainTimetablegiao diện.
  2. ElectricTrainTimetable, thực hiện giao diện này.
  3. Mã máy khách tương tác với hệ thống tệp thông qua lớp này.
  4. TimetableDisplaylớp khách hàng. Phương thức của nó printTimetable()sử dụng các phương thức của ElectricTrainTimetablelớp.
Sơ đồ rất đơn giản: Mẫu thiết kế proxy: - 2Hiện tại, với mỗi lần gọi printTimetable()phương thức, ElectricTrainTimetablelớp truy cập đĩa, tải dữ liệu và trình bày cho máy khách. Hệ thống hoạt động bình thường, nhưng nó rất chậm. Do đó, quyết định được đưa ra là tăng hiệu suất hệ thống bằng cách thêm cơ chế lưu vào 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: - 3Do đó, TimetableDisplaylớp thậm chí không nhận thấy rằng nó đang tương tác với ElectricTrainTimetableProxylớp thay vì lớp cũ. Việc triển khai mới tải thời gian biểu mỗi ngày một lần. Đối với các yêu cầu lặp lại, nó trả về đối tượng đã tải trước đó từ bộ nhớ.

Nhiệm vụ nào là tốt nhất cho một proxy?

Dưới đây là một vài tình huống mà mẫu này chắc chắn sẽ có ích:
  1. Bộ nhớ đệm
  2. Khởi tạo bị trì hoãn hoặc lười biếng Tại sao phải tải một đối tượng ngay lập tức nếu bạn có thể tải nó khi cần?
  3. Yêu cầu ghi nhật ký
  4. Xác minh trung gian dữ liệu và quyền truy cập
  5. Bắt đầu chủ đề công nhân
  6. Ghi lại quyền truy cập vào một đối tượng
Và cũng có những trường hợp sử dụng khác. Hiểu nguyên tắc đằng sau mô hình này, bạn có thể xác định các tình huống mà nó có thể được áp dụng thành công. Thoạt nhìn, proxy thực hiện công việc tương tự như facade , nhưng thực tế không phải vậy. Một proxy có cùng giao diện với đối tượng dịch vụ. Ngoài ra, đừng nhầm lẫn mẫu này với các mẫu trang trí hoặc Bộ điều hợp . Trình trang trí cung cấp giao diện mở rộng và bộ điều hợp cung cấp 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
  • + Các khả năng bổ sung liên quan đến quản lý vòng đời của đối tượng dịch vụ
  • + Nó hoạt động mà không cần đối tượng dịch vụ
  • + Nó cải thiện hiệu suất và bảo mật mã.
  • - Có nguy cơ hiệu suất có thể trở nên tồi tệ hơn do các yêu cầu bổ sung
  • - Nó làm cho hệ thống phân cấp lớp phức tạp hơn

Mẫu proxy trong thực tế

Hãy triển khai một hệ thống đọc lịch trình tàu từ đĩa cứng:

public interface TrainTimetable {
   String[] getTimetable();
   String getTrainDepartureTime();
}
Đây là lớp cài đặt giao diện chính:

public class ElectricTrainTimetable implements TrainTimetable {

   @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 nhận được thời gian biểu của chuyến tàu, chương trình sẽ đọc một tệp từ đĩa. Nhưng đó chỉ là khởi đầu của những rắc rối của chúng tôi. Toàn bộ tệp được đọc mỗi khi bạn nhận được thời gian biểu cho dù chỉ 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ụ về những việc không nên làm :) Lớp máy khách:

public class TimetableDisplay {
   private TrainTimetable trainTimetable = new ElectricTrainTimetable();

   public void printTimetable() {
       String[] timetable = trainTimetable.getTimetable();
       String[] tmpArr;
       System.out.println("Train\\tFrom\\tTo\\t\\tDeparture time\\tArrival time\\tTravel time");
       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;London;Prague;13:43;21:15;07:32
BA-1404;Paris;Graz;14:25;21:25;07:00
9B-8710;Prague;Vienna;04:48;08:49;04:01;
9B-8122;Prague;Graz;04:48;08:49;04:01
Hãy kiểm tra nó:

public static void main(String[] args) {
   TimetableDisplay timetableDisplay = new timetableDisplay();
   timetableDisplay.printTimetable();
}
Đầu ra:

Train  From  To  Departure time  Arrival time  Travel time
9B-6854  London  Prague  13:43  21:15  07:32
BA-1404  Paris  Graz  14:25  21:25  07:00
9B-8710  Prague  Vienna  04:48  08:49  04:01
9B-8122  Prague  Graz  04:48  08:49  04:01
Bây giờ, hãy xem qua các bước cần thiết để giới thiệu mẫu của chúng ta:
  1. Xác định giao diện cho phép sử dụng proxy thay vì đối tượng ban đầu. Trong ví dụ của chúng tôi, đây là TrainTimetable.

  2. Tạo lớp proxy. Nó phải có một tham chiếu đến đối tượng dịch vụ (tạo nó trong lớp hoặc chuyển đến hàm tạo).

    Đây là lớp proxy của chúng tôi:

    
    public class ElectricTrainTimetableProxy implements TrainTimetable {
       // Reference to the original object
       private TrainTimetable trainTimetable = new ElectricTrainTimetable();
      
       private String[] timetableCache = null
    
       @Override
       public String[] getTimetable() {
           return trainTimetable.getTimetable();
       }
    
       @Override
       public String getTrainDepartureTime(String trainId) {
           return trainTimetable.getTrainDepartureTime(trainId);
       }
      
       public void clearCache() {
           trainTimetable = null;
       }
    }
    

    Ở giai đoạn này, chúng ta chỉ đơn giản là tạo một lớp có tham chiếu đến đối tượng ban đầu và chuyển tiếp tất cả các cuộc gọi đến nó.

  3. Hãy triển khai logic của lớp proxy. Về cơ bản, các cuộc gọi luôn được chuyển hướng đến đối tượng ban đầu.

    
    public class ElectricTrainTimetableProxy implements TrainTimetable {
       // Reference to the original object
       private TrainTimetable trainTimetable = new ElectricTrainTimetable();
    
       private String[] timetableCache = null
    
       @Override
       public String[] getTimetable() {
           if (timetableCache == null) {
               timetableCache = trainTimetable.getTimetable();
           }
           return timetableCache;
       }
    
       @Override
       public String getTrainDepartureTime(String trainId) {
           if (timetableCache == null) {
               timetableCache = trainTimetable.getTimetable();
           }
           for (int i = 0; i < timetableCache.length; i++) {
               if (timetableCache[i].startsWith(trainId+";")) return timetableCache[i];
           }
           return "";
       }
    
       public void clearCache() {
           trainTimetable = null;
       }
    }
    

    Kiểm getTimetable()tra xem mảng thời gian biểu đã được lưu trữ trong bộ nhớ chưa. Nếu không, nó sẽ gửi yêu cầu tải dữ liệu từ đĩa và lưu kết quả. Nếu thời gian biểu đã được yêu cầu, nó sẽ nhanh chóng trả lại đối tượng từ bộ nhớ.

    Nhờ chức năng đơn giản của nó, phương thức getTrainDepartureTime() không cần phải chuyển hướng đến đối tượng ban đầu. Chúng tôi chỉ đơn giản là sao chép chức năng của nó theo một phương thức mới.

    Đừng làm điều này. Nếu bạn phải sao chép mã hoặc làm điều gì đó tương tự, thì có nghĩa là đã xảy ra sự cố và bạn cần xem xét lại vấn đề từ một góc độ khác. Trong ví dụ đơn giản của chúng tôi, chúng tôi không có lựa chọn nào khác. Nhưng trong các dự án thực tế, mã rất có thể sẽ được viết chính xác hơn.

  4. Trong mã máy khách, hãy tạo một đối tượng proxy thay vì đối tượng ban đầu:

    
    public class TimetableDisplay {
       // Changed reference
       private TrainTimetable trainTimetable = new ElectricTrainTimetableProxy();
    
       public void printTimetable() {
           String[] timetable = trainTimetable.getTimetable();
           String[] tmpArr;
           System.out.println("Train\\tFrom\\tTo\\t\\tDeparture time\\tArrival time\\tTravel time");
           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]);
           }
       }
    }
    

    Kiểm tra

    
    Train  From  To  Departure time  Arrival time  Travel time
    9B-6854  London  Prague  13:43  21:15  07:32
    BA-1404  Paris  Graz  14:25  21:25  07:00
    9B-8710  Prague  Vienna  04:48  08:49  04:01
    9B-8122  Prague  Graz  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 tùy chọn của một nhà máy tạo cả đối tượng gốc và đối tượng ủy quyền, tùy thuộc vào các điều kiện nhất định.

Trước khi chúng tôi nói lời tạm biệt, đây là một liên kết hữu ích

Đó là tất cả cho ngày hôm nay! Sẽ không phải là một ý kiến ​​tồi khi quay lại bài học và thử áp dụng kiến ​​thức mới của bạn vào thực tế :)
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION