विज़िटर पैटर्न
विज़िटर पैटर्न सॉफ़्टवेयर डिज़ाइन पैटर्न है और यह एल्गोरिदम विधि को ऑब्जेक्ट (कंप्यूटर विज्ञान) संरचना से भिन्न करता है। इस पृथक्करण के कारण संरचनाओं को संशोधित किए बिना ही उपिस्थित ऑब्जेक्ट संरचनाओं में नए ऑपरेशन जोड़े जा सकते हैं। यह ऑब्जेक्ट ओरिएंटेड प्रोग्रामिंग और सॉफ्टवेयर इंजीनियरिंग में विवृत/संवृत सिद्धांत का पालन करने की विधियां है।
इस प्रकार संक्षेप में, विज़िटर क्लासेस को संशोधित किए बिना, क्लास (कंप्यूटर प्रोग्रामिंग) के वर्ग में नए आभासी कार्य को जोड़ने की अनुमति देता है। इसके अतिरिक्त, विज़िटर वर्ग बनाया जाता है जो आभासी कार्य की सभी उपयुक्त विशेषज्ञताओं को प्रयुक्त करता है। विज़िटर उदाहरण संदर्भ को इनपुट के रूप में लेता है, और डबल डिस्पैच के माध्यम से लक्ष्य को कार्यान्वित करता है।
सम टाइप और पैटर्न मैचिंग वाली प्रोग्रामिंग लैंग्वेजेस विज़िटर पैटर्न के अनेक लाभों को समाप्त कर देती हैं, क्योंकि विज़िटर वर्ग ऑब्जेक्ट के प्रकार पर सरलता से शाखा लगाने में सक्षम होता है और यदि कोई नया ऑब्जेक्ट प्रकार परिभाषित किया जाता है, तब कंपाइलर त्रुटि उत्पन्न करता है। जिसे विज़िटर अभी तक संभाल नहीं पाता है,
अवलोकन
विज़िटर [1] डिजाइन पैटर्न्स तेईस प्रसिद्ध गैंग ऑफ़ फोर डिज़ाइन पैटर्न में से होता है जो यह बताता है कि फ्लैक्सिबल और पुन: रेउसबल ऑब्जेक्ट-ओरिएंटेड सॉफ़्टवेयर को डिज़ाइन करने के लिए आवर्ती डिज़ाइन समस्याओं का कैसे समाधान किया जाए, अर्थात, ऐसी वस्तुएं जिन्हें प्रयुक्त करना, परिवर्तित करना ,परीक्षण करना और पुन: उपयोग करना सरल होता है |
विज़िटर डिज़ाइन पैटर्न किन समस्याओं का समाधान कर सकता है?
- क्लासेस को किये बिना किसी वस्तु संरचना के (कुछ) वर्गों के लिए नए ऑपरेशन को परिभाषित करना संभव होना चाहिए।
जब नए संचालन की निरंतर आवश्यकता होती है और वस्तु संरचना में अनेक असंबंधित वर्ग होते हैं | तब प्रत्येक नए ऑपरेशन की आवश्यकता होने पर नए उपवर्ग जोड़ना इंफ़्लेक्सिबल है क्योंकि "[..] इन सभी ऑपरेशनों को विभिन्न नोड वर्गों में वितरित करने से ऐसी प्रणाली बनती है जिसे समझना, बनाए रखना और परिवर्तित करना कठिन होता है। [1]
विज़िटर डिज़ाइन पैटर्न किस समाधान का वर्णन करता है?
- यह भिन्न (विज़िटर) ऑब्जेक्ट को परिभाषित करें जो ऑब्जेक्ट संरचना के अवयवों पर किए जाने वाले ऑपरेशन को कार्यान्वित करता है।
- क्लाइंट ऑब्जेक्ट संरचना को ट्रैवर्सिंग करते हैं और अवयव पर डिस्पैचिंग ऑपरेशन एक्सेप्ट (विज़िटर) को कॉल करते हैं - जो स्वीकृत विज़िटर ऑब्जेक्ट के लिए (प्रतिनिधियों को) अनुरोध भेजता है । विज़िटर ऑब्जेक्ट तब अवयव पर ऑपरेशन करता है | यह (अवयव पर विजिट करता है)।
इससे नए विज़िटर ऑब्जेक्ट जोड़कर किसी ऑब्जेक्ट संरचना के वर्गों से स्वतंत्र रूप से नए ऑपरेशन बनाना संभव हो जाता है।
नीचे यूएमएल वर्ग और अनुक्रम आरेख भी देखें।
परिभाषा
डिज़ाइन पैटर्न विज़िटर को इस प्रकार परिभाषित करता है:
किसी वस्तु संरचना के अवयवों पर किए जाने वाले ऑपरेशन का प्रतिनिधित्व करना हैं। विज़िटर आपको उन अवयवों की कक्षाओं को परिवर्तित किये बिना नया ऑपरेशन परिभाषित करने देता है जिन पर यह संचालित होता है।
विज़िटर की प्रकृति इसे सार्वजनिक एपीआई में प्लग करने के लिए आदर्श पैटर्न बनाती है, इस प्रकार इसके क्लाइंटों को सोर्स को संशोधित किए बिना विज़िटिंग क्लास का उपयोग करके क्लास पर संचालन करने की अनुमति मिलती है।[2]
लाभ
संचालन को विज़िटर क्लासेस में ले जाना तब लाभदायक होता है जब
- किसी ऑब्जेक्ट संरचना पर अनेक असंबंधित संचालन की आवश्यकता होती है,
- ऑब्जेक्ट संरचना बनाने वाले वर्ग ज्ञात हैं और उनमें परिवर्तन की आशा नहीं है,
- नए ऑपरेशनों को निरंतर जोड़ने की आवश्यकता है,
- ऐसे एल्गोरिदम जिनमें ऑब्जेक्ट संरचना के अनेक वर्ग सम्मिलित होते हैं, किन्तु इसे ही स्थान पर प्रबंधित करना वांछित होता है,
- ऐसे एल्गोरिदम जिनको अनेक स्वतंत्र वर्ग पदानुक्रमों पर कार्य करने की आवश्यकता होती है।
चूँकि, इस पैटर्न का दोष यह है कि यह वर्ग पदानुक्रम में विस्तार को यह और अधिक कठिन बना देता है, क्योंकि नई क्लासेस को सामान्यतः नए की आवश्यकता होती है | यह visit
प्रत्येक विजिटर के लिए जोड़ी जाने वाली विधि हैं।
आवेदन
2डी कंप्यूटर एडेड डिजाइन (सीएडी) प्रणाली के डिज़ाइन पर विचार करें। इसके मूल में, वृत्त, रेखाएँ और चाप जैसी मूलभूत ज्यामितीय आकृतियों को दर्शाने के लिए अनेक प्रकार होते हैं। संस्थाओं को परतों में क्रमबद्ध किया गया है, और इस प्रकार पदानुक्रम के शीर्ष पर ड्राइंग होती है, जो सिर्फ परतों की सूची है, इसके साथ ही इसमें कुछ अतिरिक्त गुण भी होते हैं।
इस प्रकार के पदानुक्रम पर मौलिक ऑपरेशन ड्राइंग को सिस्टम के मूल फ़ाइल प्रारूप में सेविंग्स है। प्रथम द्रष्टि में, पदानुक्रम में सभी प्रकार के लिए स्थानीय सेविंग्स विधियों को जोड़ना स्वीकार्य कर सकता है। किन्तु इसका चित्रों को अन्य फ़ाइल स्वरूपों में सेविंग्स करने में सक्षम होना भी उपयोगी है। अनेक भिन्न-भिन्न फ़ाइल स्वरूपों में सेविंग्स के लिए और अधिक विधियों को जोड़ने से अपेक्षाकृत शुद्ध मूल ज्यामितीय डेटा संरचना शीघ्र ही अव्यवस्थित हो जाती है।
इसका समाधान करने की सरल विधियां प्रत्येक फ़ाइल प्रारूप के लिए भिन्न-भिन्न फ़ंक्शन बनाए रखना होता हैं। ऐसा सेव फ़ंक्शन ड्राइंग को इनपुट के रूप में लेता हैं, यह उसे ट्रैवर्सिंग करेगा, और उस विशिष्ट फ़ाइल प्रारूप में एन्कोड करेगा। चूंकि यह प्रत्येक जोड़े गए भिन्न-भिन्न प्रारूप के लिए किया जाता है, कार्यों के मध्य पुनरावृत्ति संचय हो जाता है। उदाहरण के लिए, किसी रैस्टर प्रारूप में वृत्त आकृति को सेविंग्स के लिए बहुत समान कोड की आवश्यकता होती है, किसी भी विशिष्ट रैस्टर रूप का उपयोग किया गया हो, और यह अन्य प्राचीन आकृतियों से भिन्न होता है। रेखाओं और बहुभुजों जैसी अन्य प्राचीन आकृतियों की स्थिति भी समान होती है। इस प्रकार, कोड ऑब्जेक्ट के माध्यम से घूमने वाला बड़ा बाहरी लूप बन जाता है, लूप के अंदर ऑब्जेक्ट के प्रकार को क्वेरी करने वाला बड़ा डिसीजन ट्री होता है। इस दृष्टिकोण के साथ और समस्या यह है कि यह अधिक सेवर में किसी आकृति को छोड़ना बहुत सरल है, या नया प्राचीन आकार प्रस्तुत किया जाता है, किन्तु सेव रूटीन केवल फ़ाइल प्रकार के लिए प्रयुक्त किया जाता है, यह किसी अन्य के लिए नहीं होता हैं, जिससे कोड एक्सटेंशन और मेंटेनेंस की समस्याएं उत्पन्न होती हैं। जैसे-जैसे ही फ़ाइल के संस्करण बढ़ते हैं, इसे बनाए रखना अधिक सम्मिश्र हो जाता है।
इसके अतिरिक्त, विज़िटर पैटर्न प्रयुक्त किया जा सकता है. यह पूरे पदानुक्रम पर तार्किक ऑपरेशन (अर्थात सेव (इमेज_ट्री)) को वर्ग (अर्थात सेवर) में एनकोड करता है जो कि ट्री को ट्रैवर्सिंग करने के लिए सामान्य विधियों को प्रयुक्त करता है और प्रारूप विशिष्ट व्यवहारों के लिए प्रयुक्त किए जाने वाले वर्चुअल हेल्पर विधियों (अर्थात सेव_सर्कल, सेव_स्क्वायर, आदि) का वर्णन करता है। सीएडी उदाहरण के स्तिथियों में, ऐसे प्रारूप विशिष्ट व्यवहार विज़िटर के उपवर्ग (अर्थात सेवरपीएनजी) द्वारा कार्यान्वित किए जाते हैं। इस प्रकार की जांचों के प्रकार और ट्रैवर्सल चरणों के सभी पुनरावृत्ति को हटा दिया जाता है। इसके अतिरिक्त, कंपाइलर अब कॉम्प्लैन्ट करता है कि क्या कोई आकृति छोड़ी गई है क्योंकि यह अब सामान्य बेस ट्रैवर्सल/सेव फ़ंक्शन द्वारा अपेक्षित होता है।
पुनरावृत्ति लूप
विज़िटर पैटर्न का उपयोग इटरेटर पैटर्न के सामान कंटेनर जैसी डेटा संरचनाओं पर पुनरावृत्ति के लिए किया जा सकता है किन्तु यह सीमित कार्यक्षमता के साथ होता हैं। [3]: 288 उदाहरण के लिए, निर्देशिका संरचना पर पुनरावृत्ति को अधिक ट्रैवर्सिंगंपरिक लूप (प्रोग्रामिंग) के अतिरिक्त फ़ंक्शन क्लास द्वारा कार्यान्वित किया जा सकता है। यह पुनरावृत्ति कोड का पुन: उपयोग करते समय प्रत्येक आइटम के लिए विज़िटर कार्यक्षमता को प्रयुक्त करके निर्देशिका सामग्री से विभिन्न उपयोगी सूचना प्राप्त करने की अनुमति देता हैं। यह स्मॉलटॉक सिस्टम में व्यापक रूप से नियोजित होता है और इसे C++ में भी पाया जा सकता है। [3]: 289 चूँकि, इस दृष्टिकोण का दोष यह है कि आप सरलता से लूप से बाहर नहीं निकल सकते हैं या समवर्ती रूप से पुनरावृत्त नहीं कर सकते हैं (समानांतर में अर्थात दो कंटेनरों को ट्रैवर्सिंग करते हुए) ऐसे ही समय में एकल i
वेरिएबल द्वारा) प्रयुक्त करता हैं। [3]: 289 इसके पश्चात् वाले को इन सुविधाओं का समर्थन करने के लिए विज़िटर के लिए अतिरिक्त कार्यक्षमता लिखने की आवश्यकता होती हैं। [3]: 289
संरचना
यूएमएल वर्ग और अनुक्रम आरेख
उपरोक्त यूएमएल क्लास आरेख में, ElementA
क्लास सीधे नया ऑपरेशन प्रयुक्त नहीं करता है। इसके अतिरिक्त,, ElementA
डिस्पैचिंग ऑपरेशन accept(visitor)
प्रयुक्त करता है जो "स्वीकृत विज़िटर ऑब्जेक्ट" visitor.visitElementA(this)
के लिए अनुरोध "प्रेषित" (प्रतिनिधि) करता है। यह Visitor1
वर्ग ऑपरेशन (visitElementA(e:ElementA)
) प्रयुक्त करता है।
यह ElementB
पर visitor.visitElementB(this)
को भेजकर accept(visitor)
प्रयुक्त करता है। और Visitor1
वर्ग ऑपरेशन (visitElementB(e:ElementB)
) प्रयुक्त करता है।
यूएमएल अनुक्रम आरेख रन-टाइम इंटरैक्शन दिखाता है | यह Client
ऑब्जेक्ट ऑब्जेक्ट संरचना (ElementA,ElementB
) के अवयवों को ट्रैवर्सिंग करता है और प्रत्येक अवयव पर accept(visitor)
कॉल करता है।
सबसे पूर्व, Client
ElementA
पर accept(visitor)
को कॉल करता है, जो स्वीकृत visitor
ऑब्जेक्ट पर visitElementA(this)
को कॉल करता है। अवयव स्वयं (this
) visitor
को दिया जाता है जिससे वह ElementA
पर "विज़िट" कर सके (operationA()
) को कॉल करें)।
इसके पश्चात् , Client
ElementB
पर accept(visitor)
को कॉल करता है, जो ElementB
पर "विज़िट" करने वाले visitor
पर visitElementB(this)
को कॉल करता है | यह operationB()
को कॉल करता है)।
वर्ग आरेख

विवरण
विज़िटर पैटर्न के लिए प्रोग्रामिंग लैंग्वेज की आवश्यकता होती है जो सिंगल डिस्पैच का समर्थन करती है, जैसा कि सामान्य ऑब्जेक्ट-ओरिएंटेड लैंग्वेजेस (जैसे सी++, जावा (प्रोग्रामिंग लैंग्वेज), स्मॉलटॉक, ऑब्जेक्टिव-C , स्विफ्ट (प्रोग्रामिंग लैंग्वेज), जावास्क्रिप्ट, पायथन (प्रोग्रामिंग लैंग्वेज सी# ) करती हैं। इस नियम के अनुसार, दो वस्तुओं पर विचार करें, जिनमें से प्रत्येक किसी न किसी प्रकार के वर्ग होते हैं | इसमें पसमाधाने को अवयव कहा जाता है, और दूसरे को विजिटर कहा जाता है।
विजिटर घोषणा करता है visit
विधि, जो अवयव के प्रत्येक वर्ग के लिए अवयव को तर्क के रूप में लेती है। विशिष्ट ठोस विज़िटर वर्ग से प्राप्त होते हैं और इन्हें visit
विधियों को कार्यान्वित करते हैं | जिनमें से प्रत्येक ऑब्जेक्ट संरचना पर कार्य करने वाले एल्गोरिदम का भाग प्रयुक्त करती है। एल्गोरिदम की स्थिति को कंक्रीट विज़िटर वर्ग द्वारा स्थानीय रूप से बनाए रखा जाता है।
अवयव विज़िटर को तर्क के रूप में लेते हुए, विज़िटर को accept
करने के लिए accept
विधि की घोषणा करता है। अवयव वर्ग से प्राप्त ठोस अवयव, accept
विधि को प्रयुक्त करते हैं। अपने सरलतम रूप में, यह विज़िटर की visit
विधि के लिए कॉल से अधिक कुछ नहीं होती है। समग्र पैटर्न अवयव, जो चाइल्ड ऑब्जेक्ट की सूची बनाए रखते हैं, सामान्यतः इन पर पुनरावृत्ति करते हैं, प्रत्येक चाइल्ड की accept
विधि को कॉल करते हैं।
क्लाइंट प्रत्यक्ष या अप्रत्यक्ष रूप से ऑब्जेक्ट संरचना बनाता है, और ठोस विज़िटरों को तुरंत बुलाता है। जब कोई ऑपरेशन निष्पादित करना होता है जिसे विज़िटर पैटर्न का उपयोग करके कार्यान्वित किया जाता है, तब यह शीर्ष-स्तरीय अवयव की accept
विधि को कॉल करता है।
जब प्रोग्राम में accept
विधि को बुलाया जाता है, तब इसका कार्यान्वयन अवयव के गतिशील प्रकार और विज़िटर के स्थिर प्रकार दोनों के आधार पर चुना जाता है। जब संबंधित visit
विधि को बुलाया जाता है, तब इसको कार्यान्वयन विज़िटर के गतिशील प्रकार और अवयव के स्थिर प्रकार दोनों के आधार पर चुना जाता है, जैसा कि accept
विधि को कार्यान्वयन के अंदर से जाना जाता है, जो कि गतिशील प्रकार के समान अवयव है। इसमें (बोनस के रूप में, यदि विज़िटर दिए गए अवयव के प्रकार के तर्क को संभाल नहीं सकता है, तब कंपाइलर इसकी त्रुटि को पकड़ लेता हैं।)
इस प्रकार, visit
विधि का कार्यान्वयन अवयव के गतिशील प्रकार और विज़िटर के गतिशील प्रकार दोनों के आधार पर चुना जाता है। यह डबल डिस्पैच को प्रभावी रूप से प्रयुक्त करता है। उन लैंगुएजेस के लिए जिनके ऑब्जेक्ट सिस्टम मल्टीपल डिस्पैच का समर्थन करते हैं, न कि यह केवल सिंगल डिस्पैच होते हैं, जैसे कि डायनामिक लैंग्वेज रनटाइम (डीएलआर) के माध्यम से कॉमन लिस्प या सी # होता हैं, सरल फ़ंक्शन ओवरलोडिंग के उपयोग की अनुमति देकर विज़िटर पैटर्न के कार्यान्वयन को बहुत सरल बनाया जाता है | इसके (ए.के. डायनेमिक विज़िटर) होते हैं। और देखी जा रही सभी स्तिथियों को कवर करते हैं। गतिशील विज़िटर, निःसंदेह वह केवल सार्वजनिक डेटा पर कार्य करता हैं | यह विवृत/संवृत सिद्धांत (क्योंकि यह वर्तमान संरचनाओं को संशोधित नहीं करता है) और सिंगल रिस्पांसिबिलिटी प्रिंसिपल (क्योंकि यह विज़िटर पैटर्न को भिन्न घटक में प्रयुक्त करता है) के अनुरूप है।
इस तरह, अवयवों के ग्राफ को ट्रैवर्सिंग करने के लिए एल्गोरिदम लिखा जा सकता है | इसको अवयवों और विजिटरों दोनों के गतिशील प्रकारों के आधार पर अवयवों के साथ वार्तालाप करने के लिए विभिन्न प्रकार के विजिटरों की आपूर्ति करके उस ट्रैवर्सल के समय अनेक भिन्न-भिन्न प्रकार के ऑपरेशन किए जा सकते हैं।
सी उदाहरण
यह उदाहरण भिन्न घोषणा करता है ExpressionPrintingVisitor
वह वर्ग जो प्रिंटिंग का ध्यान रखता है।
using System;
namespace Wikipedia;
public class ExpressionPrintingVisitor
{
public void PrintLiteral(Literal literal)
{
Console.WriteLine(literal.Value);
}
public void PrintAddition(Addition addition)
{
double leftValue = addition.Left.GetValue();
double rightValue = addition.Right.GetValue();
var sum = addition.GetValue();
Console.WriteLine("{0} + {1} = {2}", leftValue, rightValue, sum);
}
}
public abstract class Expression
{
public abstract void Accept(ExpressionPrintingVisitor v);
public abstract double GetValue();
}
public class Literal : Expression
{
public double Value { get; set; }
public Literal(double value)
{
this.Value = value;
}
public override void Accept(ExpressionPrintingVisitor v)
{
v.PrintLiteral(this);
}
public override double GetValue()
{
return Value;
}
}
public class Addition : Expression
{
public Expression Left { get; set; }
public Expression Right { get; set; }
public Addition(Expression left, Expression right)
{
Left = left;
Right = right;
}
public override void Accept(ExpressionPrintingVisitor v)
{
Left.Accept(v);
Right.Accept(v);
v.PrintAddition(this);
}
public override double GetValue()
{
return Left.GetValue() + Right.GetValue();
}
}
public static class Program
{
public static void Main(string[] args)
{
// Emulate 1 + 2 + 3
var e = new Addition(
new Addition(
new Literal(1),
new Literal(2)
),
new Literal(3)
);
var printingVisitor = new ExpressionPrintingVisitor();
e.Accept(printingVisitor);
}
}
स्मॉलटॉक उदाहरण
इस स्तिथियों में, यह जानना ऑब्जेक्ट का उत्तरदायित्व है कि स्ट्रीम पर स्वयं को कैसे प्रिंट किया जाए। यहाँ विजिटर धारा नहीं वस्तु होती है |
"There's no syntax for creating a class. Classes are created by sending messages to other classes."
WriteStream subclass: #ExpressionPrinter
instanceVariableNames: ''
classVariableNames: ''
package: 'Wikipedia'.
ExpressionPrinter>>write: anObject
"Delegates the action to the object. The object doesn't need to be of any special
class; it only needs to be able to understand the message #putOn:"
anObject putOn: self.
^ anObject.
Object subclass: #Expression
instanceVariableNames: ''
classVariableNames: ''
package: 'Wikipedia'.
Expression subclass: #Literal
instanceVariableNames: 'value'
classVariableNames: ''
package: 'Wikipedia'.
Literal class>>with: aValue
"Class method for building an instance of the Literal class"
^ self new
value: aValue;
yourself.
Literal>>value: aValue
"Setter for value"
value := aValue.
Literal>>putOn: aStream
"A Literal object knows how to print itself"
aStream nextPutAll: value asString.
Expression subclass: #Addition
instanceVariableNames: 'left right'
classVariableNames: ''
package: 'Wikipedia'.
Addition class>>left: a right: b
"Class method for building an instance of the Addition class"
^ self new
left: a;
right: b;
yourself.
Addition>>left: anExpression
"Setter for left"
left := anExpression.
Addition>>right: anExpression
"Setter for right"
right := anExpression.
Addition>>putOn: aStream
"An Addition object knows how to print itself"
aStream nextPut: $(.
left putOn: aStream.
aStream nextPut: $+.
right putOn: aStream.
aStream nextPut: $).
Object subclass: #Program
instanceVariableNames: ''
classVariableNames: ''
package: 'Wikipedia'.
Program>>main
| expression stream |
expression := Addition
left: (Addition
left: (Literal with: 1)
right: (Literal with: 2))
right: (Literal with: 3).
stream := ExpressionPrinter on: (String new: 100).
stream write: expression.
Transcript show: stream contents.
Transcript flush.
गो
गो ओवरलोडिंग का समर्थन नहीं करता है, इसलिए विज़िट विधियों को भिन्न-भिन्न नामों की आवश्यकता होती है। यह विशिष्ट विज़िटर इंटरफ़ेस हो सकता है |
type Visitor interface {
visitWheel(wheel Wheel) string
visitEngine(engine Engine) string
visitBody(body Body) string
visitCar(car Car) string
}
जावा उदाहरण
निम्नलिखित उदाहरण जावा लैंग्वेज (प्रोग्रामिंग लैंग्वेज) में है, और दिखाता है कि नोड्स के ट्री की सामग्री (इस स्तिथियों में कार के घटकों के वर्णन) को कैसे प्रिंट किया जा सकता है। प्रत्येक नोड उपवर्ग (Wheel
, Engine
, Body
, और Car
), के लिए print
विधियाँ बनाने के अतिरिक्त, विज़िटर वर्ग (CarElementPrintVisitor
) आवश्यक प्रिंटिंग क्रिया करता है। क्योंकि भिन्न-भिन्न नोड उपवर्गों को ठीक से प्रिंट करने के लिए थोड़ी भिन्न क्रियाओं की आवश्यकता होती है | यह CarElementPrintVisitor
अपनी visit
विधि में दिए गए तर्क के वर्ग के आधार पर क्रियाएँ भेजता है। यहCarElementDoVisitor
हैं, जो भिन्न फ़ाइल प्रारूप के लिए सेव ऑपरेशन के समान है, वह इस प्रकार कार्य करता है।
आरेख
सोर्स
import java.util.List;
interface CarElement {
void accept(CarElementVisitor visitor);
}
interface CarElementVisitor {
void visit(Body body);
void visit(Car car);
void visit(Engine engine);
void visit(Wheel wheel);
}
class Wheel implements CarElement {
private final String name;
public Wheel(final String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public void accept(CarElementVisitor visitor) {
/*
* accept(CarElementVisitor) in Wheel implements
* accept(CarElementVisitor) in CarElement, so the call
* to accept is bound at run time. This can be considered
* the *first* dispatch. However, the decision to call
* visit(Wheel) (as opposed to visit(Engine) etc.) can be
* made during compile time since 'this' is known at compile
* time to be a Wheel. Moreover, each implementation of
* CarElementVisitor implements the visit(Wheel), which is
* another decision that is made at run time. This can be
* considered the *second* dispatch.
*/
visitor.visit(this);
}
}
class Body implements CarElement {
@Override
public void accept(CarElementVisitor visitor) {
visitor.visit(this);
}
}
class Engine implements CarElement {
@Override
public void accept(CarElementVisitor visitor) {
visitor.visit(this);
}
}
class Car implements CarElement {
private final List<CarElement> elements;
public Car() {
this.elements = List.of(
new Wheel("front left"), new Wheel("front right"),
new Wheel("back left"), new Wheel("back right"),
new Body(), new Engine()
);
}
@Override
public void accept(CarElementVisitor visitor) {
for (CarElement element : elements) {
element.accept(visitor);
}
visitor.visit(this);
}
}
class CarElementDoVisitor implements CarElementVisitor {
@Override
public void visit(Body body) {
System.out.println("Moving my body");
}
@Override
public void visit(Car car) {
System.out.println("Starting my car");
}
@Override
public void visit(Wheel wheel) {
System.out.println("Kicking my " + wheel.getName() + " wheel");
}
@Override
public void visit(Engine engine) {
System.out.println("Starting my engine");
}
}
class CarElementPrintVisitor implements CarElementVisitor {
@Override
public void visit(Body body) {
System.out.println("Visiting body");
}
@Override
public void visit(Car car) {
System.out.println("Visiting car");
}
@Override
public void visit(Engine engine) {
System.out.println("Visiting engine");
}
@Override
public void visit(Wheel wheel) {
System.out.println("Visiting " + wheel.getName() + " wheel");
}
}
public class VisitorDemo {
public static void main(final String[] args) {
Car car = new Car();
car.accept(new CarElementPrintVisitor());
car.accept(new CarElementDoVisitor());
}
}
आउटपुट
Visiting front left wheel
Visiting front right wheel
Visiting back left wheel
Visiting back right wheel
Visiting body
Visiting engine
Visiting car
Kicking my front left wheel
Kicking my front right wheel
Kicking my back left wheel
Kicking my back right wheel
Moving my body
Starting my engine
Starting my car
सामान्य लिस्प उदाहरण
सोर्स
(defclass auto ()
((elements :initarg :elements)))
(defclass auto-part ()
((name :initarg :name :initform "<unnamed-car-part>")))
(defmethod print-object ((p auto-part) stream)
(print-object (slot-value p 'name) stream))
(defclass wheel (auto-part) ())
(defclass body (auto-part) ())
(defclass engine (auto-part) ())
(defgeneric traverse (function object other-object))
(defmethod traverse (function (a auto) other-object)
(with-slots (elements) a
(dolist (e elements)
(funcall function e other-object))))
;; do-something visitations
;; catch all
(defmethod do-something (object other-object)
(format t "don't know how ~s and ~s should interact~%" object other-object))
;; visitation involving wheel and integer
(defmethod do-something ((object wheel) (other-object integer))
(format t "kicking wheel ~s ~s times~%" object other-object))
;; visitation involving wheel and symbol
(defmethod do-something ((object wheel) (other-object symbol))
(format t "kicking wheel ~s symbolically using symbol ~s~%" object other-object))
(defmethod do-something ((object engine) (other-object integer))
(format t "starting engine ~s ~s times~%" object other-object))
(defmethod do-something ((object engine) (other-object symbol))
(format t "starting engine ~s symbolically using symbol ~s~%" object other-object))
(let ((a (make-instance 'auto
:elements `(,(make-instance 'wheel :name "front-left-wheel")
,(make-instance 'wheel :name "front-right-wheel")
,(make-instance 'wheel :name "rear-left-wheel")
,(make-instance 'wheel :name "rear-right-wheel")
,(make-instance 'body :name "body")
,(make-instance 'engine :name "engine")))))
;; traverse to print elements
;; stream *standard-output* plays the role of other-object here
(traverse #'print a *standard-output*)
(terpri) ;; print newline
;; traverse with arbitrary context from other object
(traverse #'do-something a 42)
;; traverse with arbitrary context from other object
(traverse #'do-something a 'abc))
आउटपुट
"front-left-wheel" "front-right-wheel" "rear-left-wheel" "rear-right-wheel" "body" "engine" kicking wheel "front-left-wheel" 42 times kicking wheel "front-right-wheel" 42 times kicking wheel "rear-left-wheel" 42 times kicking wheel "rear-right-wheel" 42 times don't know how "body" and 42 should interact starting engine "engine" 42 times kicking wheel "front-left-wheel" symbolically using symbol ABC kicking wheel "front-right-wheel" symbolically using symbol ABC kicking wheel "rear-left-wheel" symbolically using symbol ABC kicking wheel "rear-right-wheel" symbolically using symbol ABC don't know how "body" and ABC should interact starting engine "engine" symbolically using symbol ABC
टिप्पणियाँ
traverse
में other-object
पैरामीटर अतिश्योक्तिपूर्ण होते है। इसका कारण यह है कि अज्ञात फ़ंक्शन का उपयोग करना संभव है जो वांछित लक्ष्य विधि को शाब्दिक रूप से कैप्चर की गई वस्तु के साथ कॉल करता है |
(defmethod traverse (function (a auto)) ;; other-object removed
(with-slots (elements) a
(dolist (e elements)
(funcall function e)))) ;; from here too
;; ...
;; alternative way to print-traverse
(traverse (lambda (o) (print o *standard-output*)) a)
;; alternative way to do-something with
;; elements of a and integer 42
(traverse (lambda (o) (do-something o 42)) a)
अब, अज्ञात फ़ंक्शन के मुख्य भाग से प्रयुक्त कॉल में मल्टीपल डिस्पैच होता है, और इसलिए traverse
केवल मैपिंग फ़ंक्शन होता है जो किसी ऑब्जेक्ट के अवयवों पर फ़ंक्शन एप्लिकेशन को वितरित करता है। इस प्रकार मैपिंग फ़ंक्शन को छोड़कर, विज़िटर पैटर्न के सभी चिन्ह विलुप्त हो जाते हैं, जिसमें दो वस्तुओं के सम्मिलित होने का कोई प्रमाण नहीं है। दो वस्तुओं के होने और उनके प्रकारों पर डिस्पैच का समस्त ज्ञान लैम्ब्डा फ़ंक्शन में होता है।
पायथन उदाहरण
पायथन मौलिक अर्थ में मेथड ओवरलोडिंग (ट्रैवर्सिंगित मापदंडों के प्रकार के अनुसार बहुरूपी व्यवहार) का समर्थन नहीं करता है, इसलिए विभिन्न मॉडल प्रकारों के लिए विज़िट विधियों के भिन्न-भिन्न नाम होने चाहिए।
सोर्स
"""
Visitor pattern example.
"""
from abc import ABCMeta, abstractmethod
NOT_IMPLEMENTED = "You should implement this."
class CarElement(metaclass=ABCMeta):
@abstractmethod
def accept(self, visitor):
raise NotImplementedError(NOT_IMPLEMENTED)
class Body(CarElement):
def accept(self, visitor):
visitor.visitBody(self)
class Engine(CarElement):
def accept(self, visitor):
visitor.visitEngine(self)
class Wheel(CarElement):
def __init__(self, name):
self.name = name
def accept(self, visitor):
visitor.visitWheel(self)
class Car(CarElement):
def __init__(self):
self.elements = [
Wheel("front left"), Wheel("front right"),
Wheel("back left"), Wheel("back right"),
Body(), Engine()
]
def accept(self, visitor):
for element in self.elements:
element.accept(visitor)
visitor.visitCar(self)
class CarElementVisitor(metaclass=ABCMeta):
@abstractmethod
def visitBody(self, element):
raise NotImplementedError(NOT_IMPLEMENTED)
@abstractmethod
def visitEngine(self, element):
raise NotImplementedError(NOT_IMPLEMENTED)
@abstractmethod
def visitWheel(self, element):
raise NotImplementedError(NOT_IMPLEMENTED)
@abstractmethod
def visitCar(self, element):
raise NotImplementedError(NOT_IMPLEMENTED)
class CarElementDoVisitor(CarElementVisitor):
def visitBody(self, body):
print("Moving my body.")
def visitCar(self, car):
print("Starting my car.")
def visitWheel(self, wheel):
print("Kicking my {} wheel.".format(wheel.name))
def visitEngine(self, engine):
print("Starting my engine.")
class CarElementPrintVisitor(CarElementVisitor):
def visitBody(self, body):
print("Visiting body.")
def visitCar(self, car):
print("Visiting car.")
def visitWheel(self, wheel):
print("Visiting {} wheel.".format(wheel.name))
def visitEngine(self, engine):
print("Visiting engine.")
car = Car()
car.accept(CarElementPrintVisitor())
car.accept(CarElementDoVisitor())
आउटपुट
Visiting front left wheel.
Visiting front right wheel.
Visiting back left wheel.
Visiting back right wheel.
Visiting body.
Visiting engine.
Visiting car.
Kicking my front left wheel.
Kicking my front right wheel.
Kicking my back left wheel.
Kicking my back right wheel.
Moving my body.
Starting my engine.
Starting my car.
एब्सट्रेक्शन
पायथन 3 या इसके पश्चात् के संस्करण का उपयोग करने से स्वीकृति विधि का सामान्य कार्यान्वयन करने की अनुमति मिलती है |
class Visitable:
def accept(self, visitor):
lookup = "visit_" + type(self).__qualname__.replace(".", "_")
return getattr(visitor, lookup)(self)
यदि कोई पूर्व से कार्यान्वित क्लासेस पर वापस आना चाहता है तब कोई इसे क्लास के विधि समाधान क्रम पर पुनरावृत्त करने के लिए बढ़ा सकता है। वह लुकअप को पूर्व से परिभाषित करने के लिए उपवर्ग हुक सुविधा का भी उपयोग कर सकते हैं।
रिलेटेड डिज़ाइन पैटर्न
- इटरेटर पैटर्न - विज़िटर पैटर्न की तरह यह ट्रैवर्सल सिद्धांत को परिभाषित करता है,और यह ट्रैवर्स किए गए ऑब्जेक्ट के अंदर किसी भी प्रकार का भेदभाव किए बिना होता हैं |
- चर्च एन्कोडिंग -कार्यात्मक प्रोग्रामिंग से संबंधित अवधारणा, जिसमें टैग किए गए यूनियन/सम प्रकारों को ऐसे प्रकारों पर "विज़िटर" के व्यवहार का उपयोग करके मॉडल तैयार किया जा सकता है, और जो विज़िटर पैटर्न को वेरिएंट और पैटर्न का अनुकरण करने में सक्षम बनाता है।
यह भी देखें
- बीजगणितीय डेटा प्रकार
- डबल डिस्पैच
- मल्टीपल डिस्पैच
- फ़ंक्शन ऑब्जेक्ट
संदर्भ
- ↑ Jump up to: 1.0 1.1 Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison Wesley. pp. 331ff. ISBN 0-201-63361-2.
{{cite book}}
: CS1 maint: multiple names: authors list (link) - ↑ Visitor pattern real-world example
- ↑ Jump up to: 3.0 3.1 3.2 3.3 Budd, Timothy (1997). ऑब्जेक्ट-ओरिएंटेड प्रोग्रामिंग का परिचय (2nd ed.). Reading, Mass.: Addison-Wesley. ISBN 0-201-82419-1. OCLC 34788238.
- ↑ "विज़िटर डिज़ाइन पैटर्न - संरचना और सहयोग". w3sDesign.com. Retrieved 2017-08-12.
- ↑ Reddy, Martin (2011). C++ के लिए API डिज़ाइन. Boston: Morgan Kaufmann. ISBN 978-0-12-385004-1. OCLC 704559821.
बाहरी संबंध


- The Visitor Family of Design Patterns at the Wayback Machine (archived October 22, 2015). Additional archives: April 12, 2004, March 5, 2002. A rough chapter from The Principles, Patterns, and Practices of Agile Software Development, Robert C. Martin, Prentice Hall
- Visitor pattern in UML and in LePUS3 (a Design Description Language)
- Article "Componentization: the Visitor Example by Bertrand Meyer and Karine Arnout, Computer (IEEE), vol. 39, no. 7, July 2006, pages 23-30.
- Article A Type-theoretic Reconstruction of the Visitor Pattern
- Article "The Essence of the Visitor Pattern" by Jens Palsberg and C. Barry Jay. 1997 IEEE-CS COMPSAC paper showing that accept() methods are unnecessary when reflection is available; introduces term 'Walkabout' for the technique.
- Article "A Time for Reflection" by Bruce Wallace – subtitled "Java 1.2's reflection capabilities eliminate burdensome accept() methods from your Visitor pattern"
- Visitor Pattern using reflection(java).
- PerfectJPattern Open Source Project, Provides a context-free and type-safe implementation of the Visitor Pattern in Java based on Delegates.
- Visitor Design Pattern