ทำไมคุณถึงต้องการพร็อกซี่?
รูปแบบนี้ช่วยแก้ปัญหาที่เกี่ยวข้องกับการควบคุมการเข้าถึงวัตถุ คุณอาจถามว่า "ทำไมเราต้องมีการควบคุมการเข้าถึง" ลองดูสองสามสถานการณ์ที่จะช่วยให้คุณเข้าใจว่าอะไรคืออะไรตัวอย่างที่ 1
ลองนึกภาพว่าเรามีโปรเจ็กต์ขนาดใหญ่ที่มีโค้ดเก่าจำนวนมาก ซึ่งมีคลาสที่รับผิดชอบในการส่งออกรายงานจากฐานข้อมูล ชั้นเรียนทำงานพร้อมกัน นั่นคือทั้งระบบไม่ได้ใช้งานในขณะที่ฐานข้อมูลประมวลผลคำขอ โดยเฉลี่ยจะใช้เวลา 30 นาทีในการสร้างรายงาน ดังนั้น กระบวนการส่งออกจึงเริ่มในเวลา 00:30 น. และฝ่ายบริหารได้รับรายงานในตอนเช้า การตรวจสอบพบว่าจะเป็นการดีกว่าหากสามารถรับรายงานได้ทันทีในช่วงเวลาทำการปกติ ไม่สามารถเลื่อนเวลาเริ่มต้นได้ และระบบไม่สามารถบล็อกได้ในขณะที่รอการตอบกลับจากฐานข้อมูล วิธีแก้ไขคือเปลี่ยนวิธีการทำงานของระบบ สร้างและส่งออกรายงานในเธรดแยกต่างหาก โซลูชันนี้จะทำให้ระบบทำงานได้ตามปกติ และผู้บริหารจะได้รับรายงานใหม่ อย่างไรก็ตาม, มีปัญหา: รหัสปัจจุบันไม่สามารถเขียนใหม่ได้เนื่องจากส่วนอื่น ๆ ของระบบใช้ฟังก์ชันการทำงาน ในกรณีนี้ เราสามารถใช้รูปแบบพร็อกซีเพื่อแนะนำคลาสพร็อกซีระดับกลางที่จะรับคำขอเพื่อส่งออกรายงาน บันทึกเวลาเริ่มต้น และเรียกใช้เธรดแยกต่างหาก เมื่อสร้างรายงานแล้ว เธรดจะสิ้นสุดลงและทุกคนก็มีความสุขตัวอย่างที่ 2
ทีมพัฒนากำลังสร้างเว็บไซต์กิจกรรม ในการรับข้อมูลเกี่ยวกับกิจกรรมใหม่ ทีมงานจะสอบถามบริการของบุคคลที่สาม ห้องสมุดส่วนตัวพิเศษอำนวยความสะดวกในการโต้ตอบกับบริการ ในระหว่างการพัฒนา พบปัญหา: ระบบของบุคคลที่สามอัปเดตข้อมูลวันละครั้ง แต่คำขอจะถูกส่งไปทุกครั้งที่ผู้ใช้รีเฟรชหน้า สิ่งนี้สร้างคำขอจำนวนมากและบริการหยุดตอบสนอง วิธีแก้ไขคือแคชการตอบสนองของบริการและส่งคืนผลลัพธ์แคชให้กับผู้เยี่ยมชมเมื่อโหลดหน้าใหม่ และอัปเดตแคชตามความจำเป็น ในกรณีนี้ รูปแบบการออกแบบพร็อกซีเป็นโซลูชันที่ยอดเยี่ยมซึ่งไม่เปลี่ยนฟังก์ชันการทำงานที่มีอยู่หลักการเบื้องหลังรูปแบบการออกแบบ
หากต้องการใช้รูปแบบนี้ คุณต้องสร้างคลาสพร็อกซี ใช้อินเทอร์เฟซของคลาสบริการ เลียนแบบพฤติกรรมสำหรับรหัสไคลเอ็นต์ ในลักษณะนี้ ไคลเอ็นต์โต้ตอบกับพร็อกซีแทนวัตถุจริง ตามกฎแล้ว คำขอทั้งหมดจะถูกส่งต่อไปยังชั้นบริการ แต่จะมีการดำเนินการเพิ่มเติมก่อนหรือหลัง พูดง่ายๆ คือ พร็อกซีเป็นเลเยอร์ระหว่างรหัสลูกค้าและวัตถุเป้าหมาย พิจารณาตัวอย่างการแคชผลการค้นหาจากฮาร์ดดิสก์รุ่นเก่าและช้ามาก สมมติว่าเรากำลังพูดถึงตารางเวลาสำหรับรถไฟฟ้าในแอปโบราณบางแอปที่ตรรกะไม่สามารถเปลี่ยนแปลงได้ ดิสก์ที่มีตารางเวลาที่อัปเดตจะถูกแทรกทุกวันในเวลาที่กำหนด ดังนั้นเราจึงมี:TrainTimetable
อินเตอร์เฟซ.ElectricTrainTimetable
ซึ่งใช้อินเทอร์เฟซนี้- รหัสไคลเอนต์โต้ตอบกับระบบไฟล์ผ่านคลาสนี้
TimetableDisplay
คลาสลูกค้า วิธีการ ของมันprintTimetable()
ใช้วิธีการของElectricTrainTimetable
ชั้นเรียน
printTimetable()
คลาสElectricTrainTimetable
จะเข้าถึงดิสก์ โหลดข้อมูล และนำเสนอไปยังไคลเอ็นต์ ระบบใช้งานได้ดีแต่ช้ามาก เป็นผลให้มีการตัดสินใจที่จะเพิ่มประสิทธิภาพของระบบโดยการเพิ่มกลไกการแคช ซึ่งสามารถทำได้โดยใช้รูปแบบพร็อกซี: ดังนั้นTimetableDisplay
คลาสไม่ได้สังเกตว่ากำลังโต้ตอบกับElectricTrainTimetableProxy
คลาสแทนที่จะเป็นคลาสเก่า การใช้งานใหม่จะโหลดตารางเวลาวันละครั้ง สำหรับการร้องขอซ้ำ จะส่งคืนวัตถุที่โหลดไว้ก่อนหน้านี้จากหน่วยความจำ
งานอะไรดีที่สุดสำหรับพรอกซี
ต่อไปนี้เป็นบางสถานการณ์ที่รูปแบบนี้จะมีประโยชน์อย่างแน่นอน:- เก็บเอาไว้
- การเริ่มต้นล่าช้าหรือขี้เกียจ ทำไมต้องโหลดวัตถุทันทีหากคุณสามารถโหลดได้ตามต้องการ
- คำขอบันทึก
- การตรวจสอบข้อมูลและการเข้าถึงระดับกลาง
- กำลังเริ่มต้นเธรดผู้ปฏิบัติงาน
- การบันทึกการเข้าถึงวัตถุ
ข้อดีและข้อเสีย
- + คุณสามารถควบคุมการเข้าถึงวัตถุบริการได้ตามที่คุณต้องการ
- + ความสามารถเพิ่มเติมที่เกี่ยวข้องกับการจัดการวงจรชีวิตของวัตถุบริการ
- + ทำงานโดยไม่มีวัตถุบริการ
- + ปรับปรุงประสิทธิภาพและความปลอดภัยของรหัส
- - มีความเสี่ยงที่ประสิทธิภาพอาจแย่ลงเนื่องจากมีคำขอเพิ่มเติม
- - มันทำให้ลำดับชั้นของชั้นเรียนซับซ้อนขึ้น
รูปแบบตัวแทนในทางปฏิบัติ
มาใช้ระบบที่อ่านตารางเวลารถไฟจากฮาร์ดดิสก์:
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
ตอนนี้ มาดูขั้นตอนที่จำเป็นในการแนะนำรูปแบบของเรา:
-
กำหนดอินเทอร์เฟซที่อนุญาตให้ใช้พร็อกซีแทนวัตถุดั้งเดิม ในตัวอย่างของเรา นี่
TrainTimetable
คือ -
สร้างคลาสพร็อกซี ควรมีการอ้างอิงถึงวัตถุบริการ (สร้างในชั้นเรียนหรือส่งไปยังตัวสร้าง)
นี่คือคลาสพร็อกซีของเรา:
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; } }
ในขั้นตอนนี้ เรากำลังสร้างคลาสที่มีการอ้างอิงถึงออบเจกต์ดั้งเดิมและส่งต่อการเรียกทั้งหมดไปยังคลาสนั้น
-
ลองใช้ตรรกะของคลาสพร็อกซี โดยทั่วไป การโทรจะถูกเปลี่ยนเส้นทางไปยังวัตถุดั้งเดิมเสมอ
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() จึงไม่จำเป็นต้องเปลี่ยนเส้นทางไปยังวัตถุดั้งเดิม เราเพียงจำลองการทำงานของมันในวิธีการใหม่
อย่าทำเช่นนี้ หากคุณต้องทำซ้ำโค้ดหรือทำสิ่งที่คล้ายกัน แสดงว่ามีบางอย่างผิดพลาด และคุณต้องมองปัญหาอีกครั้งจากมุมที่ต่างออกไป ในตัวอย่างง่ายๆ ของเรา เราไม่มีทางเลือกอื่น แต่ในโครงการจริง โค้ดมักจะเขียนได้ถูกต้องกว่า
-
ในรหัสไคลเอ็นต์ ให้สร้างวัตถุพร็อกซีแทนวัตถุดั้งเดิม:
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
เยี่ยม มันทำงานได้อย่างถูกต้อง
คุณยังสามารถพิจารณาตัวเลือกของโรงงานที่สร้างทั้งวัตถุดั้งเดิมและวัตถุพร็อกซี ขึ้นอยู่กับเงื่อนไขบางประการ
GO TO FULL VERSION