java - जावा: एक आगंतुक से बचने के लिए एक रनटाइम अपवाद का उपयोग करना




exception visitor (5)

आप अपने आगंतुक से एक मूल्य क्यों लौट रहे हैं? विज़िटर की उचित विधि को उन कक्षाओं के द्वारा बुलाया जाता है जिन्हें विज़िट किया जा रहा है। सभी काम किया आगंतुक वर्ग के भीतर encapsulated है, यह कुछ भी नहीं वापस और इसे अपनी त्रुटियों को संभालना चाहिए कॉलिंग क्लास के लिए आवश्यक एकमात्र दायित्व, उचित यात्रा XXX विधि को कॉल करना है, कुछ और नहीं। (यह मानता है कि आप अपने उदाहरण के रूप में ओवरलोड किए गए तरीकों का उपयोग कर रहे हैं, क्योंकि प्रत्येक प्रकार के लिए समान यात्रा () विधि को ओवरराइड करने का विरोध करते हैं)।

विज़िटर द्वारा विज़िड क्लास को बदला नहीं जाना चाहिए या इसके बारे में कोई जानकारी नहीं होनी चाहिए, इसके अलावा यह यात्रा के होने की अनुमति देता है। एक मूल्य वापस करना या अपवाद फेंकना इस का उल्लंघन करेगा

विज़िटर पैटर्न

जावा प्रोग्राम में एक शॉर्ट-सर्किट कंट्रोल-फ्लो निर्माण के रूप में मुझे अनचाहे अपवाद का उपयोग करने के लिए शक्तिशाली रूप से प्रलोभन किया जा रहा है। मुझे उम्मीद है कि यहाँ कोई मुझे इस समस्या को संभालने के लिए एक बेहतर, क्लीनर तरीके से सलाह दे सकता है।

यह विचार यह है कि मैं प्रत्येक विधि कॉल में "स्टॉप" ध्वज को जांचने के बिना किसी आगंतुक द्वारा उप-पेड़ों की पुनरावर्ती अन्वेषण को कम करना चाहता हूं। विशेष रूप से, मैं एक सिंटेक्स पेड़ पर विज़िटर का उपयोग करके एक नियंत्रण प्रवाह ग्राफ का निर्माण कर रहा हूं। एएसटी में return ब्योरा उप-पेड़ के अन्वेषण को रोकना चाहिए और अगर निकटतम संलग्न / आगंतुक या लूप ब्लॉक पर आगंतुक को वापस भेजना चाहिए।

Visitor सुपरक्लास ( XTC लाइब्रेरी से ) परिभाषित करता है

Object dispatch(Node n)

जो प्रपत्र के प्रतिबिंब के माध्यम से वापस कॉल करता है

Object visitNodeSubtype(Node n)

dispatch किसी भी अपवाद फेंकने के लिए घोषित नहीं किया गया है, इसलिए मैंने एक निजी वर्ग घोषित किया जो RuntimeException फैली

private static class ReturnException extends RuntimeException {
}

अब, रिटर्न स्टेटमेंट का विज़िटर मेथड ऐसा दिखता है

Object visitReturnStatement(Node n) {
    // handle return value assignment...
    // add flow edge to exit node...
    throw new ReturnException();
}

और प्रत्येक कंपाउंड स्टेटमेंट को ReturnException को संभालना ReturnException

Object visitIfElseStatement(Node n) {
  Node test = n.getChild(0);
  Node ifPart = n.getChild(1);
  Node elsePart = n.getChild(2);

  // add flow edges to if/else... 

  try{ dispatch(ifPart); } catch( ReturnException e ) { }
  try{ dispatch(elsePart); } catch( ReturnException e ) { }
}

यह सब ठीक काम करता है:

  1. मैं कहीं एक ReturnException को पकड़ना भूल सकता हूं और कंपाइलर मुझे चेतावनी नहीं देगा।
  2. मुझे गंदा लगता है

क्या ऐसा करने के लिए इससे अच्छा तरीका है? क्या इस तरह के गैर-स्थानीय प्रवाह-नियंत्रण-को लागू करने के लिए मुझे एक जावा पैटर्न है?

[अद्यतित] यह विशिष्ट उदाहरण कुछ हद तक अमान्य हो जाता है: Visitor सुपरक्लास कैप्चर और अपवादों को लपेटता है (यहां तक ​​कि RuntimeException ), इसलिए अपवाद फेंकने से वास्तव में मदद नहीं मिलती मैंने यात्रा से एक enum प्रकार वापस करने के लिए सुझाव को कार्यान्वित किया है। सौभाग्य से, यह केवल छोटी संख्या में स्थानों की जांच करने की आवश्यकता है (उदाहरण के लिए, visitCompoundStatement ), इसलिए यह अपवाद फेंकने से वास्तव में थोड़ा कम परेशानी है।

सामान्य तौर पर, मुझे लगता है कि यह अभी भी मान्य प्रश्न है। शायद, हालांकि, यदि आप किसी तीसरे पक्ष के पुस्तकालय से जुड़े नहीं हैं, तो पूरी समस्या समझदार डिजाइन से बचा जा सकती है।


क्या आपको एक्सटीसी से आगंतुक का उपयोग करना है? यह एक बहुत ही तुच्छ इंटरफ़ेस है, और आप अपना स्वयं का पालन कर सकते हैं जो रिटर्न एक्सडेशन की जांच कर सकते हैं, जिसे आप जहां ज़रूरत पड़ने के लिए नहीं भूलेंगे


मुझे लगता है कि यह कुछ कारणों के लिए एक उचित दृष्टिकोण है:

  • आप एक तृतीय पार्टी का उपयोग कर रहे हैं और चेक अपवाद को जोड़ने में असमर्थ हैं
  • विज़िटर के बड़े सेट में हर जगह रिटर्न मान की जांच करते समय कुछ में केवल आवश्यक है कि एक अनावश्यक बोझ है

इसके अलावा, ऐसे लोग भी हैं जिन्होंने तर्क दिया है कि अनियंत्रित अपवाद सभी बुरे नहीं हैं । आपका उपयोग मुझे एक्लिप्से के ऑपरेशन कैंक्लेटेड एक्सपेशेशन की याद दिलाता है जो लंबे समय से चलने वाली पृष्ठभूमि कार्यों से बाहर निकलने के लिए उपयोग किया जाता है।

यह सही नहीं है, लेकिन, यदि अच्छी तरह से प्रलेखित किया गया है, तो यह मेरे लिए ठीक लगता है


मैं आपके लिए निम्नलिखित विकल्प देखता हूं:

  1. आगे बढ़ो और परिभाषित करें कि RuntimeException उपवर्ग dispatch लिए सबसे सामान्य कॉल में अपने अपवाद को पकड़कर गंभीर समस्याओं की जांच करें और रिपोर्ट करें कि यदि वह उस दूर तक पहुंचाए।
  2. नोड प्रसंस्करण कोड एक विशेष ऑब्जेक्ट वापस लौटते हैं यदि ऐसा लगता है कि खोज अचानक होनी चाहिए यह अभी भी आप अपवादों को पकड़ने के बदले रिटर्न वैल्यू की जांच करने के लिए मजबूर करता है, लेकिन आपको कोड की रूपरेखा को बेहतर ढंग से पसंद करना चाहिए।
  3. यदि पेड़ की सैर कुछ बाहरी कारक द्वारा रोका जा सकता है, सब सबथ्रेट के अंदर इसे करें, और उस ऑब्जेक्ट में एक सिंक्रनाइज़ किया गया फ़ील्ड सेट करें ताकि थ्रेड को समय से पहले बंद कर दिया जाए।

नियंत्रण तर्क के रूप में एक रनटाइम अपवाद फेंकना निश्चित रूप से एक बुरा विचार है। आपको गंदे महसूस होने का कारण यह है कि आप टाइप सिस्टम को बायपास कर रहे हैं, अर्थात् आपके तरीके की वापसी प्रकार एक झूठ है

आपके पास कई विकल्प हैं जो काफी अधिक स्वच्छ हैं।

1. अपवाद फ़ैक्टर

उपयोग करने के लिए एक अच्छी तकनीक, जब आप अपवादों में प्रतिबंधित होते हैं, तो आप फेंक सकते हैं, अगर आप चेक अपवाद नहीं फेंक सकते हैं, तो एक ऑब्जेक्ट वापस लौटाएं जो चेक अपवाद को फेंक देगा। java.util.concurrent.Callable इस functor का एक उदाहरण है, उदाहरण के लिए।

इस तकनीक की विस्तृत व्याख्या के लिए यहां देखें।

उदाहरण के लिए, इसके बदले:

public Something visit(Node n) {
  if (n.someting())
     return new Something();
  else
     throw new Error("Remember to catch me!");
}

यह करो:

public Callable<Something> visit(final Node n) {
  return new Callable<Something>() {
    public Something call() throws Exception {
      if (n.something())
         return new Something();
      else
         throw new Exception("Unforgettable!");
    }
  };
}

2. संघ विघटन (उर्फ द बिफनक्टर)

यह तकनीक आपको एक ही विधि से दो भिन्न प्रकारों में से एक वापस करने देता है। यह Tuple<A, B> तकनीक की तरह थोड़ा सा है, जो कि अधिकांश लोगों को एक विधि से एक से अधिक मान लौटने के लिए परिचित हैं। हालांकि, दोनों प्रकार ए और बी के मूल्यों को लौटने के बजाय, इसमें एक या बी प्रकार का एक भी मूल्य वापस करना शामिल है।

उदाहरण के लिए, एक गणना विफल, जो लागू त्रुटि कोडों की गणना कर सकता है, उदाहरण बन जाता है ...

public Either<Fail, Something> visit(final Node n) {
  if (n.something())
    return Either.<Fail, Something>right(new Something());
  else
    return Either.<Fail, Something>left(Fail.DONE);
}

कॉल करना अब बहुत साफ है क्योंकि आपको कोशिश / पकड़ की आवश्यकता नहीं है:

Either<Fail, Something> x = node.dispatch(visitor);
for (Something s : x.rightProjection()) {
  // Do something with Something
}
for (Fail f : x.leftProjection()) {
  // Handle failure
}

या तो क्लास लिखना बहुत मुश्किल नहीं है, लेकिन फ़ंक्शनल जावा लाइब्रेरी द्वारा एक पूर्ण विशेषताओं का कार्यान्वयन प्रदान किया गया है

3. विकल्प मोनाद

एक प्रकार की सुरक्षित नल की तरह थोड़ा सा, यह तब उपयोगी तकनीक है जब आप कुछ इनपुटों के लिए एक मान वापस नहीं करना चाहते हैं, लेकिन आपको अपवाद या त्रुटि कोड की आवश्यकता नहीं है। सामान्यतया, लोगों को "सतही मूल्य" कहा जाता है, लेकिन विकल्प बहुत क्लीनर होगा।

अब आपके पास है ...

public Option<Something> visit(final Node n) {
  if (n.something())
    return Option.some(new Something());
  else
    return Option.<Something>none();
}    

कॉल अच्छा और साफ है:

Option<Something> s = node.dispatch(visitor));
if (s.isSome()) {
  Something x = s.some();
  // Do something with x.
}
else {
  // Handle None.
}

और यह तथ्य कि यह एक मोनद है, आपको विशेष से निपटने के बिना चेन कॉल की सुविधा देता है कोई नहीं मान:

public Option<Something> visit(final Node n) {
  return dispatch(getIfPart(n).orElse(dispatch(getElsePart(n)));
}    

विकल्प क्लास कहीं से भी लिखना आसान है, लेकिन फिर से, फ़ंक्शनल जावा लाइब्रेरी द्वारा एक पूर्ण विशेषताओं का कार्यान्वयन प्रदान किया गया है

विकल्प की विस्तृत चर्चा के लिए यहां देखें और या तो







callcc