बड़ी वस्तु पदानुक्रम के साथ आगंतुक पैटर्न का उपयोग करना


12

प्रसंग

मैं वस्तुओं के पदानुक्रम (एक अभिव्यक्ति वृक्ष) के साथ एक "छद्म" आगंतुक पैटर्न (छद्म) का उपयोग कर रहा हूं, क्योंकि इसमें दोहरे प्रेषण का उपयोग नहीं किया गया है):

 public interface MyInterface
 {
      void Accept(SomeClass operationClass);
 }

 public class MyImpl : MyInterface 
 {
      public void Accept(SomeClass operationClass)
      {   
           operationClass.DoSomething();
           operationClass.DoSomethingElse();
           // ... and so on ...
      }
 }

यह डिजाइन, हालांकि, प्रश्नात्मक, बहुत सहज था क्योंकि MyInterface के कार्यान्वयन की संख्या महत्वपूर्ण है (~ 50 या अधिक) और मुझे अतिरिक्त संचालन जोड़ने की आवश्यकता नहीं थी।

प्रत्येक कार्यान्वयन अद्वितीय है (यह एक अलग अभिव्यक्ति या ऑपरेटर है), और कुछ कंपोजिट हैं (यानी, ऑपरेटर नोड्स जिसमें अन्य ऑपरेटर / पत्ती नोड्स शामिल होंगे)।

ट्रावर्सल को वर्तमान में ट्री के रूट नोड पर एक्सेप्ट ऑपरेशन को कॉल करके किया जाता है, जो बदले में अपने प्रत्येक बच्चे के नोड्स पर कॉल स्वीकार करता है, जो बदले में ... और इसी तरह ...

लेकिन समय आ गया है जहाँ मुझे एक नया ऑपरेशन जोड़ने की जरूरत है , जैसे कि सुंदर छपाई:

 public class MyImpl : MyInterface 
 {
      // Property does not come from MyInterface
      public string SomeProperty { get; set; }

      public void Accept(SomeClass operationClass)
      {   
           operationClass.DoSomething();
           operationClass.DoSomethingElse();
           // ... and so on ...
      }

      public void Accept(SomePrettyPrinter printer)
      {
           printer.PrettyPrint(this.SomeProperty);
      }
 }    

मुझे मूल रूप से दो विकल्प दिखाई देते हैं:

  • रख-रखाव की कीमत पर, प्रत्येक व्युत्पन्न वर्ग के लिए मेरे संचालन के लिए एक नया तरीका जोड़ते हुए एक ही डिज़ाइन रखें (एक विकल्प नहीं, IMHO)
  • एक्स्टेंसिबिलिटी की कीमत पर "ट्रू" विज़िटर पैटर्न का उपयोग करें (एक विकल्प नहीं, जैसा कि मुझे उम्मीद है कि रास्ते में और अधिक कार्यान्वयन आने वाले हैं ...), यात्रा विधि के लगभग 50+ ओवरलोड के साथ, प्रत्येक एक विशिष्ट कार्यान्वयन से मेल खाता है। ?

सवाल

क्या आप विज़िटर पैटर्न का उपयोग करने की सिफारिश करेंगे? क्या कोई अन्य पैटर्न है जो इस मुद्दे को हल करने में मदद कर सकता है?


1
शायद सज्जाकारों की एक श्रृंखला अधिक उपयुक्त होगी?
मैटवेवी

कुछ प्रश्न: ये कार्यान्वयन कैसे भिन्न होते हैं? पदानुक्रम की संरचना क्या है? और क्या यह हमेशा एक ही संरचना है? क्या आपको हमेशा उसी क्रम में संरचना को पार करने की आवश्यकता है?
जे.के.

@MattDavey: इसलिए आप कार्यान्वयन और संचालन के लिए एक डेकोरेटर रखने की सिफारिश करेंगे?
टी। फाब्रे

2
@ T.Fabre यह बताना मुश्किल है। 50+ कार्यान्वयनकर्ता हैं MyInterface.. क्या उन सभी वर्गों का एक अद्वितीय कार्यान्वयन है DoSomethingऔर DoSomethingElse? मैं यह नहीं देखता कि आपका आगंतुक वर्ग वास्तव में पदानुक्रम का पता कैसे facade
लगाता है

सी # का भी कौन सा संस्करण है। क्या आपके पास लंबोदर हैं? या linq? अपने निपटान में
जे.के.

जवाबों:


13

मैं तीन प्रोग्रामिंग भाषाओं में छह बड़े पैमाने पर परियोजनाओं पर 10+ वर्षों के दौरान अभिव्यक्ति पेड़ों का प्रतिनिधित्व करने के लिए आगंतुक पैटर्न का उपयोग कर रहा हूं, और मैं परिणाम से बहुत खुश हूं। मुझे कुछ चीजें मिलीं जिन्होंने पैटर्न को लागू करना बहुत आसान बना दिया:

आगंतुक के इंटरफ़ेस में ओवरलोड का उपयोग न करें

टाइप को विधि नाम में रखें, अर्थात उपयोग करें

IExpressionVisitor {
    void VisitPrimitive(IPrimitiveExpression expr);
    void VisitComposite(ICompositeExpression expr);
}

बजाय

IExpressionVisitor {
    void Visit(IPrimitiveExpression expr);
    void Visit(ICompositeExpression expr);
}

अपने विज़िटर इंटरफ़ेस में "कैच अज्ञात" विधि जोड़ें।

यह उन उपयोगकर्ताओं के लिए संभव होगा जो आपके कोड को संशोधित नहीं कर सकते:

IExpressionVisitor {
    void VisitPrimitive(IPrimitiveExpression expr);
    void VisitComposite(ICompositeExpression expr);
    void VisitExpression(IExpression expr);
};

यह उन्हें अपने कैच-ऑल मेथड के कार्यान्वयन में रन-टाइम प्रकार की जानकारी का उपयोग करके उनके भावों को समझने IExpressionऔर IVisitorउनके "भाव" को समझने देता है VisitExpression

IVisitorइंटरफ़ेस का एक डिफ़ॉल्ट डू-नॉन इम्प्लीमेंटेशन प्रदान करें

यह उन उपयोगकर्ताओं को अनुमति देता है जिन्हें अभिव्यक्ति के सबसेट से निपटने की आवश्यकता होती है जो अपने आगंतुकों का तेजी से निर्माण करते हैं, और आपके कोड इम्यून को आपके लिए अधिक विधियाँ जोड़ते हैं IVisitor। उदाहरण के लिए, एक विज़िटर लिखना जो आपके भावों से सभी चर नामों को काटता है, एक आसान काम बन जाता है, और कोड तब भी नहीं टूटेगा जब आप अपने IVisitorबाद में नए अभिव्यक्ति प्रकारों का एक गुच्छा जोड़ते हैं ।


2
क्या आप स्पष्ट कर सकते हैं कि आप क्यों कहते हैं Do not use overloads in the interface of the visitor?
स्टीवन एवर्स

1
क्या आप बता सकते हैं कि आप ओवरलोड का उपयोग करने की सिफारिश क्यों नहीं करते हैं? मैंने कहीं पढ़ा (oodesign.com पर, वास्तव में) कि यह वास्तव में मायने नहीं रखता कि मैं ओवरलोड का उपयोग करता हूं या नहीं। क्या कोई विशिष्ट कारण है कि आप उस डिज़ाइन को पसंद करते हैं?
टी। फाब्रे

2
@ T.Fabre यह गति के मामले में कोई फर्क नहीं पड़ता है, लेकिन यह पठनीयता के मामले में मायने रखता है। तीन में से दो भाषाओं में विधि रिज़ॉल्यूशन जहां मैंने इसे लागू किया है ( जावा और सी #) को संभावित ओवरलोड के बीच चयन करने के लिए एक रन-टाइम कदम की आवश्यकता होती है, जिससे कोड की अधिक संख्या के साथ कोड को पढ़ना थोड़ा कठिन हो जाता है। कोड को फिर से भरना भी आसान हो जाता है, क्योंकि जिस विधि को आप संशोधित करना चाहते हैं उसे चुनना एक तुच्छ कार्य बन जाता है।
dasblinkenlight

@ SnOrfus कृपया ऊपर T.Fabre पर मेरा उत्तर देखें।
dasblinkenlight

@dasblinkenlight C # अब रनटाइम तय करने के लिए डायनामिक प्रदान करता है कि ओवरलोड विधि का उपयोग क्या किया जाना चाहिए (कंपाउंड समय पर नहीं)। क्या अब भी कोई कारण है कि ओवरलोडिंग का उपयोग क्यों न किया जाए?
टिंटेनफिस्क
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.