डबल प्रेषण इस पैटर्न का उपयोग करने के लिए दूसरों के बीच सिर्फ एक कारण है ।
लेकिन ध्यान दें कि यह एकल प्रेषण प्रतिमान का उपयोग करने वाली भाषाओं में दोहरे या अधिक प्रेषण को लागू करने का एकमात्र तरीका है।
यहाँ पैटर्न का उपयोग करने के कारण हैं:
1) हम हर बार मॉडल को बदलने के बिना नए संचालन को परिभाषित करना चाहते हैं क्योंकि मॉडल अक्सर नहीं बदलता है, अक्सर संचालन में बदलाव होता है।
2) हम मॉडल और व्यवहार को युगल नहीं करना चाहते हैं क्योंकि हम कई अनुप्रयोगों में एक पुन: प्रयोज्य मॉडल रखना चाहते हैं या हम एक एक्स्टेंसिबल मॉडल चाहते हैं जो क्लाइंट कक्षाओं को अपने स्वयं के कक्षाओं के साथ अपने व्यवहार को परिभाषित करने की अनुमति दें।
3) हमारे पास सामान्य ऑपरेशन हैं जो मॉडल के ठोस प्रकार पर निर्भर करते हैं लेकिन हम प्रत्येक उपवर्ग में तर्क को लागू नहीं करना चाहते हैं क्योंकि यह कई वर्गों में और कई स्थानों पर सामान्य तर्क को विस्फोट करेगा ।
4) हम एक डोमेन मॉडल डिजाइन और एक ही पदानुक्रम के मॉडल वर्गों का उपयोग कर रहे हैं बहुत अधिक विशिष्ट चीजें हैं जो कहीं और इकट्ठा की जा सकती हैं ।
5) हमें दोहरे प्रेषण की आवश्यकता है ।
हमारे पास इंटरफ़ेस प्रकारों के साथ घोषित किए गए चर हैं और हम उनका रनटाइम प्रकार के अनुसार उन्हें संसाधित करने में सक्षम होना चाहते हैं… बेशक बिना if (myObj instanceof Foo) {}
किसी चाल या उपयोग के ।
विचार इन उदाहरणों को उन तरीकों से पारित करने के लिए है जो एक विशिष्ट प्रसंस्करण को लागू करने के लिए एक ठोस प्रकार के इंटरफ़ेस को पैरामीटर के रूप में घोषित करते हैं। ऐसा करने का यह तरीका संभव नहीं है कि भाषाओं के साथ बॉक्स एकल-प्रेषण पर निर्भर हो क्योंकि रनटाइम पर चुना गया चयन केवल रिसीवर के रनटाइम प्रकार पर निर्भर करता है।
ध्यान दें कि जावा में, कॉल करने का तरीका (हस्ताक्षर) संकलन समय पर चुना जाता है और यह घोषित प्रकार के मापदंडों पर निर्भर करता है, न कि उनके रनटाइम प्रकार पर।
अंतिम बिंदु जो आगंतुक का उपयोग करने का एक कारण है, यह भी एक परिणाम है क्योंकि जैसा कि आप आगंतुक को लागू करते हैं (निश्चित रूप से उन भाषाओं के लिए जो कई प्रेषण का समर्थन नहीं करते हैं), आपको आवश्यक रूप से एक दोहरे प्रेषण कार्यान्वयन की आवश्यकता है।
ध्यान दें कि प्रत्येक पर आगंतुक को लागू करने के लिए तत्वों (पुनरावृत्ति) का ट्रावेल पैटर्न का उपयोग करने का एक कारण नहीं है।
आप पैटर्न का उपयोग करते हैं क्योंकि आप मॉडल और प्रसंस्करण को विभाजित करते हैं।
और पैटर्न का उपयोग करके, आप एक पुनरावृत्ति क्षमता के अलावा लाभ उठाते हैं।
यह क्षमता बहुत शक्तिशाली है और accept()
एक सामान्य विधि के साथ सामान्य प्रकार पर चलना से परे है।
यह एक विशेष उपयोग का मामला है। तो मैं इसे एक तरफ रख दूंगा।
जावा में उदाहरण
मैं एक शतरंज उदाहरण के साथ पैटर्न के अतिरिक्त मूल्य का वर्णन करूंगा जहां हम प्रसंस्करण को परिभाषित करना चाहते हैं क्योंकि खिलाड़ी एक टुकड़े को स्थानांतरित करने का अनुरोध करता है।
विज़िटर पैटर्न के उपयोग के बिना, हम टुकड़ों में चल रहे व्यवहार को सीधे टुकड़ों के उपवर्गों में परिभाषित कर सकते हैं।
हम उदाहरण के लिए एक Piece
इंटरफ़ेस जैसे हो सकते हैं :
public interface Piece{
boolean checkMoveValidity(Coordinates coord);
void performMove(Coordinates coord);
Piece computeIfKingCheck();
}
प्रत्येक टुकड़ा उपवर्ग इसे इस तरह लागू करेगा:
public class Pawn implements Piece{
@Override
public boolean checkMoveValidity(Coordinates coord) {
...
}
@Override
public void performMove(Coordinates coord) {
...
}
@Override
public Piece computeIfKingCheck() {
...
}
}
और सभी टुकड़ा उपवर्गों के लिए एक ही बात।
यहाँ एक आरेख वर्ग है जो इस डिज़ाइन को दिखाता है:
यह दृष्टिकोण तीन महत्वपूर्ण कमियां प्रस्तुत करता है:
- जैसे व्यवहार performMove()
या computeIfKingCheck()
शायद बहुत आम तर्क का उपयोग करेंगे।
उदाहरण के लिए जो भी ठोस है Piece
, performMove()
वह वर्तमान टुकड़े को एक विशिष्ट स्थान पर सेट करेगा और संभावित रूप से प्रतिद्वंद्वी टुकड़ा ले जाएगा।
संबंधित व्यवहारों को कई वर्गों में विभाजित करने के बजाय उन्हें किसी तरह से पराजित करना एकल जिम्मेदारी पैटर्न है। उनकी स्थिरता को कठिन बना रहा है।
- प्रसंस्करण के रूप में checkMoveValidity()
कुछ ऐसा नहीं होना चाहिए जो Piece
उपवर्गों को देख या बदल सकता है।
यह जाँच है कि मानव या कंप्यूटर क्रियाओं से परे है। यह चेक प्रत्येक खिलाड़ी द्वारा अनुरोधित कार्रवाई पर किया जाता है ताकि यह सुनिश्चित हो सके कि अनुरोधित टुकड़ा चाल वैध है।
तो हम भी Piece
इंटरफ़ेस में प्रदान नहीं करना चाहते हैं ।
- बॉट डेवलपर्स के लिए चुनौतीपूर्ण शतरंज के खेल में, आम तौर पर एप्लिकेशन एक मानक एपीआई ( Piece
इंटरफेस, उपवर्ग, बोर्ड, सामान्य व्यवहार आदि) प्रदान करता है और डेवलपर्स को अपनी बॉट रणनीति को समृद्ध करने देता है।
ऐसा करने में सक्षम होने के लिए, हमें एक मॉडल का प्रस्ताव करना होगा जहां डेटा और व्यवहार को Piece
कार्यान्वयन में कसकर जोड़ा नहीं जाता है।
तो चलिए विजिटर पैटर्न का उपयोग करते है!
हमारे पास दो प्रकार की संरचना है:
- मॉडल कक्षाएं जो दौरा किया जाना स्वीकार करती हैं (टुकड़े)
- वे आगंतुक जो उनसे मिलने जाते हैं (ऑपरेशन चला रहे हैं)
यहाँ एक वर्ग आरेख है जो पैटर्न को दिखाता है:
ऊपरी हिस्से में हमारे पास आगंतुक हैं और निचले हिस्से में हमारे पास मॉडल कक्षाएं हैं।
यहाँ PieceMovingVisitor
इंटरफ़ेस (प्रत्येक प्रकार के लिए निर्दिष्ट व्यवहार Piece
) है:
public interface PieceMovingVisitor {
void visitPawn(Pawn pawn);
void visitKing(King king);
void visitQueen(Queen queen);
void visitKnight(Knight knight);
void visitRook(Rook rook);
void visitBishop(Bishop bishop);
}
टुकड़ा अब परिभाषित किया गया है:
public interface Piece {
void accept(PieceMovingVisitor pieceVisitor);
Coordinates getCoordinates();
void setCoordinates(Coordinates coordinates);
}
इसकी प्रमुख विधि है:
void accept(PieceMovingVisitor pieceVisitor);
यह पहला प्रेषण प्रदान करता है: Piece
रिसीवर के आधार पर एक आह्वान ।
संकलित समय पर, विधि accept()
टुकड़ा इंटरफ़ेस की विधि के लिए बाध्य है और रनटाइम पर, बाध्य विधि को रनटाइम Piece
वर्ग पर लागू किया जाएगा ।
और यह accept()
एक दूसरा प्रेषण करेगा जो विधि कार्यान्वयन है।
वास्तव में, प्रत्येक Piece
उपवर्ग जो किसी PieceMovingVisitor
वस्तु द्वारा जाना चाहता है, वह PieceMovingVisitor.visit()
तर्क के रूप में पारित करके विधि को आमंत्रित करता है।
इस तरह, संकलक समय के रूप में जल्द ही संकलित करता है, ठोस प्रकार के साथ घोषित पैरामीटर का प्रकार।
दूसरा प्रेषण है।
यहाँ Bishop
उपवर्ग है जो दिखाता है कि:
public class Bishop implements Piece {
private Coordinates coord;
public Bishop(Coordinates coord) {
super(coord);
}
@Override
public void accept(PieceMovingVisitor pieceVisitor) {
pieceVisitor.visitBishop(this);
}
@Override
public Coordinates getCoordinates() {
return coordinates;
}
@Override
public void setCoordinates(Coordinates coordinates) {
this.coordinates = coordinates;
}
}
और यहाँ एक उपयोग उदाहरण है:
// 1. Player requests a move for a specific piece
Piece piece = selectPiece();
Coordinates coord = selectCoordinates();
// 2. We check with MoveCheckingVisitor that the request is valid
final MoveCheckingVisitor moveCheckingVisitor = new MoveCheckingVisitor(coord);
piece.accept(moveCheckingVisitor);
// 3. If the move is valid, MovePerformingVisitor performs the move
if (moveCheckingVisitor.isValid()) {
piece.accept(new MovePerformingVisitor(coord));
}
आगंतुक कमियां
विज़िटर पैटर्न एक बहुत शक्तिशाली पैटर्न है लेकिन इसकी कुछ महत्वपूर्ण सीमाएँ भी हैं जिनका उपयोग करने से पहले आपको इस पर विचार करना चाहिए।
1) एनकैप्सुलेशन को कम करने / तोड़ने का जोखिम
कुछ प्रकार के ऑपरेशन में, विज़िटर पैटर्न डोमेन ऑब्जेक्ट्स के इनकैप्सुलेशन को कम या तोड़ सकता है।
उदाहरण के लिए, जैसा कि MovePerformingVisitor
वर्ग को वास्तविक टुकड़े के निर्देशांक सेट करने की आवश्यकता होती है, Piece
इंटरफ़ेस को ऐसा करने का एक तरीका प्रदान करना होगा:
void setCoordinates(Coordinates coordinates);
की जिम्मेदारी है Piece
निर्देशांक परिवर्तनों अब Piece
उपवर्गों की तुलना में अन्य वर्गों के लिए खुली है । उपवर्गों
में आगंतुक द्वारा किए गए प्रसंस्करण को आगे Piece
बढ़ाना कोई विकल्प नहीं है।
यह वास्तव में Piece.accept()
किसी भी आगंतुक कार्यान्वयन के रूप में एक और मुद्दा बना देगा । यह नहीं पता है कि आगंतुक क्या करता है और इसलिए टुकड़ा स्थिति को बदलने और कैसे करने के बारे में कोई विचार नहीं है।
आगंतुक की पहचान करने का एक तरीका Piece.accept()
आगंतुक कार्यान्वयन के अनुसार पोस्ट प्रोसेसिंग करना होगा। यह एक बहुत ही बुरा विचार हो के रूप में यह आगंतुक कार्यान्वयन और टुकड़ा उपवर्गों के बीच और यह शायद के रूप में चाल का उपयोग करने की आवश्यकता होगी के अलावा एक उच्च युग्मन बनाने होगा getClass()
, instanceof
या किसी मार्कर आगंतुक कार्यान्वयन की पहचान।
2) मॉडल को बदलने की आवश्यकता
Decorator
उदाहरण के लिए कुछ अन्य व्यवहार डिजाइन पैटर्न के विपरीत , आगंतुक पैटर्न घुसपैठ है।
हमें वास्तव में accept()
विज़िट किए जाने के लिए एक विधि प्रदान करने के लिए प्रारंभिक रिसीवर वर्ग को संशोधित करने की आवश्यकता है ।
हमारे पास Piece
इसके उपवर्गों के लिए कोई समस्या नहीं थी क्योंकि ये हमारी कक्षाएं हैं ।
अंतर्निहित या तीसरे पक्ष के वर्गों में, चीजें इतनी आसान नहीं होती हैं।
हमें accept()
विधि जोड़ने के लिए उन्हें लपेटना या इनहेरिट करना होगा (यदि हम कर सकते हैं) ।
३) अप्रत्यक्ष
पैटर्न कई गुना अप्रत्यक्ष बनाता है।
दोहरे प्रेषण का अर्थ है एक के बजाय दो आह्वान:
call the visited (piece) -> that calls the visitor (pieceMovingVisitor)
और हम अतिरिक्त अप्रत्यक्ष कर सकते हैं क्योंकि आगंतुक विज़िट की गई वस्तु स्थिति को बदलता है।
यह एक चक्र की तरह लग सकता है:
call the visited (piece) -> that calls the visitor (pieceMovingVisitor) -> that calls the visited (piece)