JavaRush /จาวาบล็อก /Random-TH /รูปแบบการออกแบบพร็อกซี

รูปแบบการออกแบบพร็อกซี

เผยแพร่ในกลุ่ม
ในการเขียนโปรแกรม สิ่งสำคัญคือต้องวางแผนสถาปัตยกรรมแอปพลิเคชันอย่างเหมาะสม เครื่องมือที่ขาดไม่ได้สำหรับสิ่งนี้คือรูปแบบการออกแบบ วันนี้เราจะมาพูดถึง Proxy หรืออีกนัยหนึ่งคือ Vice

ทำไมคุณถึงต้องการรอง?

รูปแบบนี้ช่วยแก้ปัญหาที่เกี่ยวข้องกับการเข้าถึงอ็อบเจ็กต์ที่มีการควบคุม คุณอาจมีคำถาม: “เหตุใดเราจึงต้องมีการเข้าถึงที่มีการควบคุมเช่นนี้” ลองดูสถานการณ์สองสามอย่างที่จะช่วยให้คุณทราบว่าอะไรคืออะไร

ตัวอย่างที่ 1

ลองจินตนาการว่าเรามีโปรเจ็กต์ขนาดใหญ่ที่มีโค้ดเก่าๆ มากมาย โดยมีคลาสที่รับผิดชอบในการดาวน์โหลดรายงานจากฐานข้อมูล คลาสทำงานพร้อมกัน นั่นคือ ทั้งระบบไม่ได้ใช้งานในขณะที่ฐานข้อมูลประมวลผลคำร้องขอ โดยเฉลี่ยแล้ว รายงานจะถูกสร้างขึ้นภายใน 30 นาที เนื่องจากฟีเจอร์นี้ การอัปโหลดจึงเริ่มต้นเวลา 00:30 น. และฝ่ายบริหารจะได้รับรายงานนี้ในตอนเช้า ในระหว่างการวิเคราะห์ ปรากฎว่าจำเป็นต้องได้รับรายงานทันทีหลังจากสร้าง ซึ่งก็คือภายในหนึ่งวัน ไม่สามารถกำหนดเวลาการเปิดตัวใหม่ได้ เนื่องจากระบบจะรอการตอบกลับจากฐานข้อมูล วิธีแก้ไขคือเปลี่ยนหลักการทำงานโดยเริ่มการอัปโหลดและการสร้างรายงานในเธรดที่แยกจากกัน โซลูชันนี้จะช่วยให้ระบบทำงานได้ตามปกติ และฝ่ายบริหารจะได้รับรายงานใหม่ อย่างไรก็ตาม มีปัญหา: ไม่สามารถเขียนโค้ดปัจจุบันใหม่ได้ เนื่องจากส่วนอื่น ๆ ของระบบใช้ฟังก์ชันของมัน ในกรณีนี้ คุณสามารถแนะนำคลาสพร็อกซีระดับกลางได้โดยใช้รูปแบบรอง ซึ่งจะได้รับคำขอให้อัปโหลดรายงาน บันทึกเวลาเริ่มต้น และเรียกใช้เธรดแยกต่างหาก เมื่อรายงานถูกสร้างขึ้น เธรดจะทำงานให้เสร็จสิ้นและทุกคนจะมีความสุข

ตัวอย่างที่ 2

ทีมพัฒนาจัดทำเว็บไซต์โปสเตอร์ หากต้องการรับข้อมูลเกี่ยวกับเหตุการณ์ใหม่ พวกเขาหันไปใช้บริการของบุคคลที่สามซึ่งมีการโต้ตอบผ่านห้องสมุดปิดพิเศษ ในระหว่างการพัฒนา เกิดปัญหา: ระบบบุคคลที่สามอัปเดตข้อมูลวันละครั้ง และคำขอเกิดขึ้นทุกครั้งที่ผู้ใช้รีเฟรชเพจ สิ่งนี้จะสร้างคำขอจำนวนมากและบริการหยุดการตอบสนอง วิธีแก้ไขคือการแคชการตอบสนองของบริการและให้ผลลัพธ์ที่บันทึกไว้แก่ผู้เยี่ยมชมในการรีบูตแต่ละครั้ง โดยอัปเดตแคชนี้ตามความจำเป็น ในกรณีนี้ การใช้รูปแบบรองเป็นวิธีแก้ปัญหาที่ยอดเยี่ยมโดยไม่ต้องเปลี่ยนฟังก์ชันการทำงานที่เสร็จสมบูรณ์

รูปแบบทำงานอย่างไร

หากต้องการใช้รูปแบบนี้ คุณต้องสร้างคลาสพร็อกซี ใช้อินเทอร์เฟซระดับบริการ จำลองพฤติกรรมสำหรับโค้ดไคลเอ็นต์ ดังนั้น แทนที่จะเป็นวัตถุจริง ไคลเอนต์จะโต้ตอบกับพร็อกซีของมัน โดยทั่วไปแล้ว คำขอทั้งหมดจะถูกส่งไปยังคลาสบริการ แต่มีการดำเนินการเพิ่มเติมก่อนหรือหลังการโทร พูดง่ายๆ ก็คือ พร็อกซีออบเจ็กต์นี้เป็นเลเยอร์ระหว่างโค้ดไคลเอ็นต์และออบเจ็กต์เป้าหมาย ลองดูตัวอย่างการแคชคำขอจากดิสก์เก่าที่ช้ามาก ปล่อยให้เป็นตารางรถไฟฟ้าในแอปพลิเคชั่นโบราณบางตัวซึ่งหลักการทำงานไม่สามารถเปลี่ยนแปลงได้ ดิสก์ที่มีกำหนดเวลาที่อัปเดตจะถูกแทรกทุกวันตามเวลาที่กำหนด ดังนั้นเราจึงมี:
  1. อินเตอร์เฟซTimetableTrains.
  2. คลาสTimetableElectricTrainsที่ใช้อินเทอร์เฟซนี้
  3. ผ่านคลาสนี้ที่โค้ดไคลเอ็นต์โต้ตอบกับระบบไฟล์ของดิสก์
  4. คลาสลูกค้าDisplayTimetable. วิธีการของมันprintTimetable()ใช้วิธีการTimetableElectricTrainsเรียน
รูปแบบนั้นเรียบง่าย: รูปแบบการออกแบบพร็อกซี - 2ในปัจจุบัน ทุกครั้งที่มีการเรียกใช้เมธอด คลาส จะเข้าถึง printTimetable()ดิสก์TimetableElectricTrainsยกเลิกการโหลดข้อมูล และจัดเตรียมให้กับไคลเอนต์ ระบบนี้ทำงานได้ดีแต่ช้ามาก ดังนั้นจึงตัดสินใจเพิ่มประสิทธิภาพของระบบโดยการเพิ่มกลไกการแคช ซึ่งสามารถทำได้โดยใช้รูปแบบ Proxy: รูปแบบการออกแบบพร็อกซี - 3วิธีนี้จะทำให้ชั้นเรียนDisplayTimetableไม่สังเกตเห็นด้วยซ้ำว่ากำลังโต้ตอบกับชั้นเรียนTimetableElectricTrainsProxyไม่ใช่กับชั้นเรียนก่อนหน้า การใช้งานใหม่จะโหลดกำหนดการวันละครั้ง และเมื่อมีการร้องขอซ้ำๆ จะส่งคืนออบเจ็กต์ที่โหลดไว้แล้วจากหน่วยความจำ

ควรใช้ Proxy สำหรับงานใด?

ต่อไปนี้เป็นบางสถานการณ์ที่รูปแบบนี้จะมีประโยชน์อย่างแน่นอน:
  1. เก็บเอาไว้.
  2. การใช้งานแบบ Lazy เรียกอีกอย่างว่าการใช้งานแบบ Lazy เหตุใดจึงต้องโหลดออบเจ็กต์ทั้งหมดในครั้งเดียว ในเมื่อคุณสามารถโหลดได้ตามต้องการ
  3. คำขอบันทึก
  4. ข้อมูลระหว่างกาลและการตรวจสอบการเข้าถึง
  5. เปิดตัวเธรดการประมวลผลแบบขนาน
  6. การบันทึกหรือการนับประวัติการโทร
มีกรณีการใช้งานอื่นๆ เช่นกัน เมื่อทำความเข้าใจหลักการทำงานของรูปแบบนี้แล้วคุณจะพบแอปพลิเคชันที่ประสบความสำเร็จได้ด้วยตัวเอง เมื่อมองแวบแรกรองก็ทำแบบเดียวกับFacadeแต่ไม่ใช่ พร็อกซีมีอินเทอร์เฟซเดียวกันกับวัตถุบริการ นอกจากนี้อย่าสับสนกับรูปแบบด้วยมัณฑนากรหรืออะแดปเตอร์ มัณฑนากรมีอินเทอร์เฟซแบบขยาย ในขณะที่อะแดปเตอร์มีอินเทอร์เฟซสำรอง

ข้อดีและข้อเสีย

  • + คุณสามารถควบคุมการเข้าถึงวัตถุบริการได้ตามที่คุณต้องการ
  • + ความสามารถเพิ่มเติมสำหรับการจัดการวงจรชีวิตของออบเจ็กต์บริการ
  • + ทำงานโดยไม่มีวัตถุบริการ
  • + ปรับปรุงประสิทธิภาพโค้ดและความปลอดภัย
  • - มีความเสี่ยงที่ประสิทธิภาพจะลดลงเนื่องจากการรักษาเพิ่มเติม
  • - ทำให้โครงสร้างของคลาสโปรแกรมซับซ้อนขึ้น

รูปแบบทดแทนในทางปฏิบัติ

ลองใช้ระบบกับคุณที่อ่านตารางรถไฟจากดิสก์:
public interface TimetableTrains {
   String[] getTimetable();
   String getTrainDepartureTime();
}
คลาสที่ใช้อินเทอร์เฟซหลัก:
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 "";
   }
}
ทุกครั้งที่คุณพยายามรับตารางเวลาของรถไฟทั้งหมด โปรแกรมจะอ่านไฟล์จากดิสก์ แต่สิ่งเหล่านี้ยังคงเป็นดอกไม้ ไฟล์จะถูกอ่านทุกครั้งที่คุณต้องการรับตารางเวลาสำหรับรถไฟขบวนเดียว! เป็นเรื่องดีที่รหัสดังกล่าวมีอยู่ในตัวอย่างที่ไม่ดีเท่านั้น :) คลาสไคลเอนต์:
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]);
       }
   }
}
ไฟล์ตัวอย่าง:

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
มาทดสอบกัน:
public static void main(String[] args) {
   DisplayTimetable displayTimetable = new DisplayTimetable();
   displayTimetable.printTimetable();
}
บทสรุป:

Поезд  Откуда  Куда   Время отправления Время прибытия    Время в пути
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
ตอนนี้เรามาดูขั้นตอนการนำรูปแบบของเราไปใช้:
  1. กำหนดอินเทอร์เฟซที่อนุญาตให้คุณใช้พร็อกซีใหม่แทนออบเจ็กต์ดั้งเดิม ในตัวอย่างของเรามันTimetableTrainsคือ

  2. สร้างคลาสพร็อกซี จะต้องมีการอ้างอิงถึงวัตถุบริการ (สร้างในคลาสหรือส่งผ่านในตัวสร้าง)

    นี่คือคลาสพร็อกซีของเรา:

    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;
       }
    }

    ในขั้นตอนนี้ เราเพียงแค่สร้างคลาสที่มีการอ้างอิงถึงออบเจ็กต์ต้นฉบับและส่งต่อการเรียกทั้งหมดไปยังคลาสนั้น

  3. เราใช้ตรรกะของคลาสพร็อกซี โดยพื้นฐานแล้วการโทรจะถูกเปลี่ยนเส้นทางไปยังออบเจ็กต์ดั้งเดิมเสมอ

    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;
       }
    }

    วิธีการgetTimetable()ตรวจสอบว่าอาร์เรย์กำหนดการถูกแคชไว้ในหน่วยความจำหรือไม่ ถ้าไม่เช่นนั้นจะออกคำขอให้โหลดข้อมูลจากดิสก์เพื่อจัดเก็บผลลัพธ์ หากคำขอกำลังทำงานอยู่ ก็จะส่งคืนออบเจ็กต์จากหน่วยความจำอย่างรวดเร็ว

    ด้วยฟังก์ชันการทำงานที่เรียบง่าย ทำให้เมธอด getTrainDeparttireTime() ไม่จำเป็นต้องเปลี่ยนเส้นทางไปยังออบเจ็กต์ต้นฉบับ เราเพียงแต่ทำซ้ำฟังก์ชันการทำงานของมันให้เป็นวิธีการใหม่

    คุณไม่สามารถทำอย่างนั้นได้ หากคุณต้องทำซ้ำโค้ดหรือดำเนินการปรับแต่งที่คล้ายกัน นั่นหมายความว่ามีบางอย่างผิดพลาด และคุณต้องมองปัญหาจากมุมที่ต่างออกไป ในตัวอย่างง่ายๆ ของเรา ไม่มีวิธีอื่น แต่ในโครงการจริง โค้ดน่าจะเขียนได้ถูกต้องมากขึ้น

  4. แทนที่การสร้างวัตถุดั้งเดิมในรหัสไคลเอนต์ด้วยวัตถุทดแทน:

    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]);
           }
       }
    }

    การตรวจสอบ

    
    Поезд  Откуда  Куда   Время отправления Время прибытия    Время в пути
    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

    เยี่ยมมาก มันทำงานได้อย่างถูกต้อง

    คุณยังสามารถพิจารณาโรงงานที่จะสร้างทั้งวัตถุดั้งเดิมและวัตถุทดแทนโดยขึ้นอยู่กับเงื่อนไขบางประการ

ลิงก์ที่มีประโยชน์แทนที่จะเป็นจุด

  1. บทความ ดีๆเกี่ยวกับรูปแบบ และเกร็ดเล็กๆ น้อยๆ เกี่ยวกับ “รองฯ”

นั่นคือทั้งหมดสำหรับวันนี้! คงจะดีไม่น้อยหากได้กลับไปเรียนรู้และทดสอบความรู้ใหม่ของคุณในทางปฏิบัติ :)
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION