CodeGym /จาวาบล็อก /สุ่ม /รูปแบบการออกแบบหนังสือมอบฉันทะ
John Squirrels
ระดับ
San Francisco

รูปแบบการออกแบบหนังสือมอบฉันทะ

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

ทำไมคุณถึงต้องการพร็อกซี่?

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

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

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

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

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

หลักการเบื้องหลังรูปแบบการออกแบบ

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

งานอะไรดีที่สุดสำหรับพรอกซี

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

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

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

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

มาใช้ระบบที่อ่านตารางเวลารถไฟจากฮาร์ดดิสก์:

public interface TrainTimetable {
   String[] getTimetable();
   String getTrainDepartureTime();
}
นี่คือคลาสที่ใช้อินเทอร์เฟซหลัก:

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 "";
   }
}
ทุกครั้งที่คุณได้รับตารางเวลารถไฟ โปรแกรมจะอ่านไฟล์จากดิสก์ แต่นั่นเป็นเพียงจุดเริ่มต้นของปัญหาของเรา ไฟล์ทั้งหมดจะถูกอ่านทุกครั้งที่คุณได้รับตารางเวลาสำหรับรถไฟแม้แต่ขบวนเดียว! เป็นการดีที่รหัสดังกล่าวมีอยู่ในตัวอย่างของสิ่งที่ไม่ควรทำเท่านั้น :) คลาสไคลเอนต์:

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]);
       }
   }
}
ไฟล์ตัวอย่าง:

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
มาทดสอบกัน:

public static void main(String[] args) {
   TimetableDisplay timetableDisplay = new timetableDisplay();
   timetableDisplay.printTimetable();
}
เอาท์พุต:

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
ตอนนี้ มาดูขั้นตอนที่จำเป็นในการแนะนำรูปแบบของเรา:
  1. กำหนดอินเทอร์เฟซที่อนุญาตให้ใช้พร็อกซีแทนวัตถุดั้งเดิม ในตัวอย่างของเรา นี่TrainTimetableคือ

  2. สร้างคลาสพร็อกซี ควรมีการอ้างอิงถึงวัตถุบริการ (สร้างในชั้นเรียนหรือส่งไปยังตัวสร้าง)

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

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

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

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

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

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

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

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

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

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

    ตรวจสอบ

    
    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
    

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

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

ก่อนที่เราจะบอกลา นี่คือลิงค์ที่เป็นประโยชน์

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