ทำไมคุณถึงต้องการรอง?
รูปแบบนี้ช่วยแก้ปัญหาที่เกี่ยวข้องกับการเข้าถึงอ็อบเจ็กต์ที่มีการควบคุม คุณอาจมีคำถาม: “เหตุใดเราจึงต้องมีการเข้าถึงที่มีการควบคุมเช่นนี้” ลองดูสถานการณ์สองสามอย่างที่จะช่วยให้คุณทราบว่าอะไรคืออะไรตัวอย่างที่ 1
ลองจินตนาการว่าเรามีโปรเจ็กต์ขนาดใหญ่ที่มีโค้ดเก่าๆ มากมาย โดยมีคลาสที่รับผิดชอบในการดาวน์โหลดรายงานจากฐานข้อมูล คลาสทำงานพร้อมกัน นั่นคือ ทั้งระบบไม่ได้ใช้งานในขณะที่ฐานข้อมูลประมวลผลคำร้องขอ โดยเฉลี่ยแล้ว รายงานจะถูกสร้างขึ้นภายใน 30 นาที เนื่องจากฟีเจอร์นี้ การอัปโหลดจึงเริ่มต้นเวลา 00:30 น. และฝ่ายบริหารจะได้รับรายงานนี้ในตอนเช้า ในระหว่างการวิเคราะห์ ปรากฎว่าจำเป็นต้องได้รับรายงานทันทีหลังจากสร้าง ซึ่งก็คือภายในหนึ่งวัน ไม่สามารถกำหนดเวลาการเปิดตัวใหม่ได้ เนื่องจากระบบจะรอการตอบกลับจากฐานข้อมูล วิธีแก้ไขคือเปลี่ยนหลักการทำงานโดยเริ่มการอัปโหลดและการสร้างรายงานในเธรดที่แยกจากกัน โซลูชันนี้จะช่วยให้ระบบทำงานได้ตามปกติ และฝ่ายบริหารจะได้รับรายงานใหม่ อย่างไรก็ตาม มีปัญหา: ไม่สามารถเขียนโค้ดปัจจุบันใหม่ได้ เนื่องจากส่วนอื่น ๆ ของระบบใช้ฟังก์ชันของมัน ในกรณีนี้ คุณสามารถแนะนำคลาสพร็อกซีระดับกลางได้โดยใช้รูปแบบรอง ซึ่งจะได้รับคำขอให้อัปโหลดรายงาน บันทึกเวลาเริ่มต้น และเรียกใช้เธรดแยกต่างหาก เมื่อรายงานถูกสร้างขึ้น เธรดจะทำงานให้เสร็จสิ้นและทุกคนจะมีความสุขตัวอย่างที่ 2
ทีมพัฒนาจัดทำเว็บไซต์โปสเตอร์ หากต้องการรับข้อมูลเกี่ยวกับเหตุการณ์ใหม่ พวกเขาหันไปใช้บริการของบุคคลที่สามซึ่งมีการโต้ตอบผ่านห้องสมุดปิดพิเศษ ในระหว่างการพัฒนา เกิดปัญหา: ระบบบุคคลที่สามอัปเดตข้อมูลวันละครั้ง และคำขอเกิดขึ้นทุกครั้งที่ผู้ใช้รีเฟรชเพจ สิ่งนี้จะสร้างคำขอจำนวนมากและบริการหยุดการตอบสนอง วิธีแก้ไขคือการแคชการตอบสนองของบริการและให้ผลลัพธ์ที่บันทึกไว้แก่ผู้เยี่ยมชมในการรีบูตแต่ละครั้ง โดยอัปเดตแคชนี้ตามความจำเป็น ในกรณีนี้ การใช้รูปแบบรองเป็นวิธีแก้ปัญหาที่ยอดเยี่ยมโดยไม่ต้องเปลี่ยนฟังก์ชันการทำงานที่เสร็จสมบูรณ์รูปแบบทำงานอย่างไร
หากต้องการใช้รูปแบบนี้ คุณต้องสร้างคลาสพร็อกซี ใช้อินเทอร์เฟซระดับบริการ จำลองพฤติกรรมสำหรับโค้ดไคลเอ็นต์ ดังนั้น แทนที่จะเป็นวัตถุจริง ไคลเอนต์จะโต้ตอบกับพร็อกซีของมัน โดยทั่วไปแล้ว คำขอทั้งหมดจะถูกส่งไปยังคลาสบริการ แต่มีการดำเนินการเพิ่มเติมก่อนหรือหลังการโทร พูดง่ายๆ ก็คือ พร็อกซีออบเจ็กต์นี้เป็นเลเยอร์ระหว่างโค้ดไคลเอ็นต์และออบเจ็กต์เป้าหมาย ลองดูตัวอย่างการแคชคำขอจากดิสก์เก่าที่ช้ามาก ปล่อยให้เป็นตารางรถไฟฟ้าในแอปพลิเคชั่นโบราณบางตัวซึ่งหลักการทำงานไม่สามารถเปลี่ยนแปลงได้ ดิสก์ที่มีกำหนดเวลาที่อัปเดตจะถูกแทรกทุกวันตามเวลาที่กำหนด ดังนั้นเราจึงมี:- อินเตอร์เฟซ
TimetableTrains
. - คลาส
TimetableElectricTrains
ที่ใช้อินเทอร์เฟซนี้ - ผ่านคลาสนี้ที่โค้ดไคลเอ็นต์โต้ตอบกับระบบไฟล์ของดิสก์
- คลาสลูกค้า
DisplayTimetable
. วิธีการของมันprintTimetable()
ใช้วิธีการTimetableElectricTrains
เรียน

printTimetable()
ดิสก์TimetableElectricTrains
ยกเลิกการโหลดข้อมูล และจัดเตรียมให้กับไคลเอนต์ ระบบนี้ทำงานได้ดีแต่ช้ามาก ดังนั้นจึงตัดสินใจเพิ่มประสิทธิภาพของระบบโดยการเพิ่มกลไกการแคช ซึ่งสามารถทำได้โดยใช้รูปแบบ Proxy: 
DisplayTimetable
ไม่สังเกตเห็นด้วยซ้ำว่ากำลังโต้ตอบกับชั้นเรียนTimetableElectricTrainsProxy
ไม่ใช่กับชั้นเรียนก่อนหน้า การใช้งานใหม่จะโหลดกำหนดการวันละครั้ง และเมื่อมีการร้องขอซ้ำๆ จะส่งคืนออบเจ็กต์ที่โหลดไว้แล้วจากหน่วยความจำ
ควรใช้ Proxy สำหรับงานใด?
ต่อไปนี้เป็นบางสถานการณ์ที่รูปแบบนี้จะมีประโยชน์อย่างแน่นอน:- เก็บเอาไว้.
- การใช้งานแบบ Lazy เรียกอีกอย่างว่าการใช้งานแบบ Lazy เหตุใดจึงต้องโหลดออบเจ็กต์ทั้งหมดในครั้งเดียว ในเมื่อคุณสามารถโหลดได้ตามต้องการ
- คำขอบันทึก
- ข้อมูลระหว่างกาลและการตรวจสอบการเข้าถึง
- เปิดตัวเธรดการประมวลผลแบบขนาน
- การบันทึกหรือการนับประวัติการโทร
ข้อดีและข้อเสีย
- + คุณสามารถควบคุมการเข้าถึงวัตถุบริการได้ตามที่คุณต้องการ
- + ความสามารถเพิ่มเติมสำหรับการจัดการวงจรชีวิตของออบเจ็กต์บริการ
- + ทำงานโดยไม่มีวัตถุบริการ
- + ปรับปรุงประสิทธิภาพโค้ดและความปลอดภัย
- - มีความเสี่ยงที่ประสิทธิภาพจะลดลงเนื่องจากการรักษาเพิ่มเติม
- - ทำให้โครงสร้างของคลาสโปรแกรมซับซ้อนขึ้น
รูปแบบทดแทนในทางปฏิบัติ
ลองใช้ระบบกับคุณที่อ่านตารางรถไฟจากดิสก์: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
ตอนนี้เรามาดูขั้นตอนการนำรูปแบบของเราไปใช้:
-
กำหนดอินเทอร์เฟซที่อนุญาตให้คุณใช้พร็อกซีใหม่แทนออบเจ็กต์ดั้งเดิม ในตัวอย่างของเรามัน
TimetableTrains
คือ -
สร้างคลาสพร็อกซี จะต้องมีการอ้างอิงถึงวัตถุบริการ (สร้างในคลาสหรือส่งผ่านในตัวสร้าง)
นี่คือคลาสพร็อกซีของเรา:
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; } }
ในขั้นตอนนี้ เราเพียงแค่สร้างคลาสที่มีการอ้างอิงถึงออบเจ็กต์ต้นฉบับและส่งต่อการเรียกทั้งหมดไปยังคลาสนั้น
-
เราใช้ตรรกะของคลาสพร็อกซี โดยพื้นฐานแล้วการโทรจะถูกเปลี่ยนเส้นทางไปยังออบเจ็กต์ดั้งเดิมเสมอ
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() ไม่จำเป็นต้องเปลี่ยนเส้นทางไปยังออบเจ็กต์ต้นฉบับ เราเพียงแต่ทำซ้ำฟังก์ชันการทำงานของมันให้เป็นวิธีการใหม่
คุณไม่สามารถทำอย่างนั้นได้ หากคุณต้องทำซ้ำโค้ดหรือดำเนินการปรับแต่งที่คล้ายกัน นั่นหมายความว่ามีบางอย่างผิดพลาด และคุณต้องมองปัญหาจากมุมที่ต่างออกไป ในตัวอย่างง่ายๆ ของเรา ไม่มีวิธีอื่น แต่ในโครงการจริง โค้ดน่าจะเขียนได้ถูกต้องมากขึ้น
-
แทนที่การสร้างวัตถุดั้งเดิมในรหัสไคลเอนต์ด้วยวัตถุทดแทน:
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
เยี่ยมมาก มันทำงานได้อย่างถูกต้อง
คุณยังสามารถพิจารณาโรงงานที่จะสร้างทั้งวัตถุดั้งเดิมและวัตถุทดแทนโดยขึ้นอยู่กับเงื่อนไขบางประการ
GO TO FULL VERSION