आपको प्रॉक्सी की आवश्यकता क्यों है?
यह पैटर्न किसी वस्तु तक नियंत्रित पहुंच से जुड़ी समस्याओं को हल करने में मदद करता है। आप पूछ सकते हैं, "हमें नियंत्रित पहुँच की आवश्यकता क्यों है?" आइए कुछ स्थितियों को देखें जो आपको यह पता लगाने में मदद करेंगी कि क्या है।उदाहरण 1
कल्पना कीजिए कि हमारे पास पुराने कोड के समूह के साथ एक बड़ी परियोजना है, जहां डेटाबेस से रिपोर्ट निर्यात करने के लिए जिम्मेदार एक वर्ग है। वर्ग समकालिक रूप से काम करता है। यही है, जब डेटाबेस अनुरोध को संसाधित करता है तो पूरा सिस्टम निष्क्रिय रहता है। एक रिपोर्ट तैयार करने में औसतन 30 मिनट लगते हैं। तदनुसार, निर्यात प्रक्रिया 12: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