दिलचस्प पोस्ट
Global.asax में सत्र का अंत कैसे नियंत्रित करें? एएसपी.नेट एमवीसी: डेटाएनेटेशन द्वारा कस्टम वैल्यूशन एचटीटीपीआरएल कनेक्शन ने एंड्रॉइड 2.x में ठीक काम किया लेकिन 4.1 में नहीं: कोई प्रमाणीकरण चुनौतियां नहीं मिलीं जावा परावर्तन का उपयोग करके सुपरक्लास विधि को कैसे कॉल किया जाए रैखिक लयआउट प्रोग्राम में मार्जिन सेट करें UIPageViewController, मैं डेटा स्रोत द्वारा निर्दिष्ट आदेश को बिना गड़बड़ किए एक विशिष्ट पृष्ठ पर कैसे सही ढंग से कूदूं? अपसेट के संस्करण का चयन करें / सम्मिलित करें: क्या उच्च संगामिति के लिए कोई डिज़ाइन पैटर्न है? पायथन कचरा कलेक्टर दस्तावेज़ दिए गए दो तारों की दूरी समानता माप की गणना कैसे करें? स्ट्रेटोल का सही उपयोग पाठ और कैलेंडर के साथ ईमेल को मल्टीपार्ट करें: आउटलुक आईसीएस को नहीं पहचानता है स्टार्टअप पर लॉन्च करने के लिए मैं एक प्रोग्राम कैसे सेट करूं? यदि आप 32-बिट int 0x80 Linux ABI 64-बिट कोड का उपयोग करते हैं तो क्या होगा? मैं एक पायथन सबप्रोसेक्शन 'स्टडिन को कैसे लिखूं? MySQL में बैच डालने के लिए कैसे करें

जब बंदर एक विधि को पैचिंग करते हैं, तो क्या आप नए कार्यान्वयन से ओवरराइड विधि को कॉल कर सकते हैं?

कहते हैं कि मैं एक वर्ग में एक विधि को बंद करने वाला बंदर हूँ, मैं ओवरराइड करने की विधि से ओवरराइड पद्धति को कैसे कॉल कर सकता हूं? Ie कुछ super तरह कुछ

उदाहरण के लिए

 class Foo def bar() "Hello" end end class Foo def bar() super() + " World" end end >> Foo.new.bar == "Hello World" 

वेब के समाधान से एकत्रित समाधान "जब बंदर एक विधि को पैचिंग करते हैं, तो क्या आप नए कार्यान्वयन से ओवरराइड विधि को कॉल कर सकते हैं?"

संपादित करें : जब मैंने मूल रूप से यह जवाब लिखा था, तब से 5 साल हो गए हैं, और इसे वर्तमान में रखने के लिए कुछ कॉस्मेटिक सर्जरी के योग्य हैं।

आप यहां संपादित करने से पहले अंतिम संस्करण देख सकते हैं।


आप नाम या कीवर्ड द्वारा अधिलेखित विधि कॉल नहीं कर सकते। यह कई कारणों में से एक है क्योंकि बंदर पैचिंग से बचा जाना चाहिए और इसके बजाय विरासत को प्राथमिकता दी जानी चाहिए, क्योंकि जाहिर है आप ओवरराइड पद्धति को कॉल कर सकते हैं।

बंदर पैचिंग से बचना

विरासत

तो, यदि संभव हो, तो आपको ऐसा कुछ पसंद करना चाहिए:

 class Foo def bar 'Hello' end end class ExtendedFoo < Foo def bar super + ' World' end end ExtendedFoo.new.bar # => 'Hello World' 

यह काम करता है, यदि आप Foo ऑब्जेक्ट्स के निर्माण को नियंत्रित करते हैं बस प्रत्येक जगह को बदलने के लिए जो एक Foo बनाने की बजाय एक ExtendedFoo Foo बनाएँ। यह बेहतर काम करता है यदि आप निर्भरता इंजेक्शन डिजाइन पैटर्न , फैक्टरी विधि डिजाइन पैटर्न , सार फैक्ट्री डिज़ाइन पैटर्न या उन पंक्तियों के साथ कुछ का उपयोग करते हैं, क्योंकि उस स्थिति में आपको जगह बदलने की जरूरत है।

शिष्ठ मंडल

यदि आप Foo ऑब्जेक्ट्स के निर्माण को नियंत्रित नहीं करते हैं, उदाहरण के लिए क्योंकि वे एक ढांचे द्वारा बनाए गए हैं जो आपके नियंत्रण से बाहर हैं (जैसे कि रूबी-ऑन-रेल उदाहरण के लिए), फिर आप रैपर डिज़ाइन पैटर्न का उपयोग कर सकते हैं:

 require 'delegate' class Foo def bar 'Hello' end end class WrappedFoo < DelegateClass(Foo) def initialize(wrapped_foo) super end def bar super + ' World' end end foo = Foo.new # this is not actually in your code, it comes from somewhere else wrapped_foo = WrappedFoo.new(foo) # this is under your control wrapped_foo.bar # => 'Hello World' 

असल में, सिस्टम की सीमा पर, जहां Foo ऑब्जेक्ट आपके कोड में आता है, आप उसे किसी अन्य ऑब्जेक्ट में लपेटते हैं, और उसके बाद उस कोड को अपने कोड में हर जगह मूल के बजाय उपयोग करें।

यह stdlib में delegate पुस्तकालय से Object#DelegateClass सहायक विधि का उपयोग करता है

"क्लीन" बंदर पैचिंग

Module#prepend प्रीपेड: मिक्सिन प्रीपेडिंग

ऊपर दो विधियों को बंदर पैचिंग से बचने के लिए सिस्टम को बदलने की आवश्यकता होती है। यह खंड बंदर पैचिंग की पसंदीदा और कम से कम आक्रामक विधि को दर्शाता है, सिस्टम को बदलना एक विकल्प नहीं होना चाहिए

Module#prepend को अधिक या कम बिल्कुल इस उपयोग के मामले में समर्थन देने के लिए जोड़ा गया था। Module#prepend ही Module#include रूप में एक ही चीज़ करता है, सिवाय इसके कि यह सीधे कक्षा के नीचे मिक्सिन में मिक्स करता है:

 class Foo def bar 'Hello' end end module FooExtensions def bar super + ' World' end end class Foo prepend FooExtensions end Foo.new.bar # => 'Hello World' 

नोट: मैंने इस प्रश्न में Module#prepend बारे में कुछ भी लिखा है: रूबी मॉड्यूल बनाम व्युत्पन्नता पहले

मिक्सिन विरासत (टूटा हुआ)

मैंने देखा है कि कुछ लोग कोशिश करते हैं (और पूछें कि यह स्टैक्स ओवरफ्लो पर यहां क्यों काम नहीं करता है) ऐसा कुछ, यानी include बजाय मिश्रण को include है:

 class Foo def bar 'Hello' end end module FooExtensions def bar super + ' World' end end class Foo include FooExtensions end 

दुर्भाग्य से, वह काम नहीं करेगा यह एक अच्छा विचार है, क्योंकि यह विरासत का उपयोग करता है, जिसका मतलब है कि आप super उपयोग कर सकते हैं हालांकि, Module#include उत्तराधिकार पदानुक्रम में वर्ग के ऊपर FooExtensions#bar होता है, जिसका मतलब है कि FooExtensions#bar कभी भी नहीं बुलाया जाएगा (और अगर इसे कहा जाता है, तो super वास्तव में Foo#bar संदर्भ नहीं बल्कि Object#bar मौजूद नहीं है), क्योंकि Foo#bar हमेशा पहले पाया जाएगा

विधि रैपिंग

बड़ा सवाल यह है कि हम वास्तव में एक वास्तविक पद्धति को बनाए रखने के बिना bar विधि को कैसे पकड़ सकते हैं? इसका जवाब झूठ है, क्योंकि यह अक्सर काम करता है, कार्यात्मक प्रोग्रामिंग में। हम एक वास्तविक वस्तु के रूप में विधि का एक पकड़ लेते हैं, और हम यह सुनिश्चित करने के लिए एक बंद (यानी ब्लॉक) का उपयोग करते हैं कि हम और केवल हम उस ऑब्जेक्ट पर रोकते हैं:

 class Foo def bar 'Hello' end end class Foo old_bar = instance_method(:bar) define_method(:bar) do old_bar.bind(self).() + ' World' end end Foo.new.bar # => 'Hello World' 

यह बहुत साफ है: चूंकि old_bar सिर्फ एक स्थानीय चर है, यह कक्षा के शरीर के अंत में गुंजाइश से बाहर हो जाएगा, और इसे कहीं से भी एक्सेस करना असंभव है, यहां तक ​​कि प्रतिबिंब भी ! और चूंकि Module#define_method एक ब्लॉक लेता है, और उनके आस-पास के वाद्ययंत्रीय वातावरण (जैसे कि हम define_method बजाय define_method का उपयोग कर रहे हैं) के define_method हैं, तो यह (और केवल ) अभी भी old_bar तक पहुंच old_bar , भले ही वह बाहर हो गया हो गुंजाइश की

लघु व्याख्या:

 old_bar = instance_method(:bar) 

यहां हम bar विधि को एक UnboundMethod विधि विधि ऑब्जेक्ट में UnboundMethod और इसे स्थानीय चर old_bar निर्दिष्ट करते हैं। इसका मतलब है, अब हमारे पास एक तरीका है जो इसे ओवरराइट करने के बाद भी bar को पकड़ने का तरीका है।

 old_bar.bind(self) 

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

खैर, यह नहीं है, यही वजह है कि हमें पहले किसी ऑब्जेक्ट के लिए हमारे UnboundMethod को bind करने की ज़रूरत है, जो कि एक ऑब्जेक्ट वापस करेगा जो हम फिर कॉल कर सकते हैं। ( UnboundMethod कॉल नहीं किया जा सकता, क्योंकि उन्हें नहीं पता कि उनके self को जानने के बिना क्या करना है।)

और हम इसे bind सकते हैं? हम इसे अपने आप से bind लें, जिस तरह से यह मूल bar तरह व्यवहार करेगा!

अन्त में, हमें उस Method को कॉल करने की आवश्यकता है जो bind से लौटा है। रूबी 1.9 में, उस ( .() ) के लिए कुछ निफ्टी नया वाक्यविन्यास है, लेकिन यदि आप 1.8 पर हैं, तो आप बस call विधि का उपयोग कर सकते हैं; यही वही है .() वैसे भी अनुवाद किया जाता है।

यहां कई अन्य प्रश्न हैं, जहां उन अवधारणाओं में से कुछ समझाए गए हैं:

  • रूबी में मैं एक समारोह का संदर्भ कैसे दूं?
  • क्या रूबी का कोड ब्लॉक सी.के. लैम्ब्डा अभिव्यक्ति के समान है?

"गंदे" बंदर पैचिंग

alias_method श्रृंखला

हमारे बंदर पैचिंग के साथ समस्या यह है कि जब हम विधि को अधिलेखित करते हैं, तो विधि समाप्त हो जाती है, इसलिए हम इसे अब और नहीं बुला सकते। तो, बस एक बैकअप प्रति बनाओ!

 class Foo def bar 'Hello' end end class Foo alias_method :old_bar, :bar def bar old_bar + ' World' end end Foo.new.bar # => 'Hello World' Foo.new.old_bar # => 'Hello' 

इस के साथ समस्या यह है कि हमने अब old_bar को एक अति आवश्यक old_bar विधि से दूषित किया है यह पद्धति हमारे प्रलेखन में दिखाई जाएगी, यह हमारे आईडीई में कोड पूरा होने में दिखाई जाएगी, यह प्रतिबिंब के दौरान दिखाया जाएगा। इसके अलावा, यह अभी भी कहा जा सकता है, लेकिन संभवतः हम इसे बंद कर चुके हैं, क्योंकि हमें पहली जगह में उसका व्यवहार पसंद नहीं था, इसलिए हम अन्य लोगों को इसे कॉल नहीं करना चाहें।

इस तथ्य के बावजूद कि इसमें कुछ अवांछनीय गुण हैं, यह दुर्भाग्य से AciveSupport के Module#alias_method_chain के माध्यम से लोकप्रिय हो गया है।

एक तरफ: परिशोधन

अगर आपको कुछ विशिष्ट स्थानों में अलग-अलग व्यवहार की आवश्यकता होती है और पूरे सिस्टम में नहीं, तो आप एक विशिष्ट क्षेत्र में बंदर पैच को प्रतिबंधित करने के लिए परिशोधन का उपयोग कर सकते हैं। मैं इसे Module#prepend का प्रयोग करके इसके ऊपर Module#prepend

 class Foo def bar 'Hello' end end module ExtendedFoo module FooExtensions def bar super + ' World' end end refine Foo do prepend FooExtensions end end Foo.new.bar # => 'Hello' # We haven't activated our Refinement yet! using ExtendedFoo # Activate our Refinement Foo.new.bar # => 'Hello World' # There it is! 

आप इस प्रश्न में परिशोधन का उपयोग करने का एक और अधिक परिष्कृत उदाहरण देख सकते हैं: विशिष्ट विधि के लिए बंदर पैच को कैसे सक्षम करें?


परित्यक्त विचार

रूबी समुदाय, Module#prepend बसने से Module#prepend , कई अलग-अलग विचारों के आसपास चल रहे थे, जिन्हें आप कभी-कभी पुराने चर्चाओं में संदर्भित देख सकते हैं। इनमें से सभी Module#prepend समाहित हैं

विधि संयोजक

एक विचार CLOS से विधि संयोजकों का विचार था। यह मूलतः आस्पेक्ट-ओरिएंटेड प्रोग्रामिंग के सबसेट का एक बहुत हल्का संस्करण है।

सिंटैक्स का प्रयोग करना जैसे

 class Foo def bar:before # will always run before bar, when bar is called end def bar:after # will always run after bar, when bar is called # may or may not be able to access and/or change bar's return value end end 

आप bar विधि के निष्पादन को "हुक" कर पाएंगे।

हालांकि यह काफी स्पष्ट नहीं है कि अगर bar के भीतर bar:after रिटर्न मूल्य तक पहुंच कैसे होगी bar:after शायद हम (एबी) super कीवर्ड का इस्तेमाल कर सकते हैं?

 class Foo def bar 'Hello' end end class Foo def bar:after super + ' World' end end 

प्रतिस्थापन

संयोजक से पहले एक मिश्रण को एक अधिलेखित विधि के साथ समेटने के बराबर होता है जो विधि के बहुत ही अंत में super कॉल करता है। इसी तरह, संयोजन के बाद एक अधिलेखित विधि के साथ एक मिक्सिन तैयार करने के बराबर होता है जो विधि की शुरुआत में super कॉल करता है।

आप super को कॉल करने से पहले और बाद में सामान भी कर सकते हैं, आप super कई बार कॉल कर सकते हैं, और super रिटर्न वैल्यू को पुनः प्राप्त कर सकते हैं और हेरफेर कर सकते हैं, जिससे विधि prepend तुलना में अधिक शक्तिशाली बना सकते हैं।

 class Foo def bar:before # will always run before bar, when bar is called end end # is the same as module BarBefore def bar # will always run before bar, when bar is called super end end class Foo prepend BarBefore end 

तथा

 class Foo def bar:after # will always run after bar, when bar is called # may or may not be able to access and/or change bar's return value end end # is the same as class BarAfter def bar original_return_value = super # will always run after bar, when bar is called # has access to and can change bar's return value end end class Foo prepend BarAfter end 

old कीवर्ड

यह विचार super समान एक नया कीवर्ड जोड़ता है, जो आपको अधिलेखित पद्धति को कॉल करने की अनुमति देता है उसी तरीके से super आपको ओवरराइड पद्धति को कॉल करने देता है :

 class Foo def bar 'Hello' end end class Foo def bar old + ' World' end end Foo.new.bar # => 'Hello World' 

इस के साथ मुख्य समस्या ये है कि यह पीछे की असंगत है: यदि आपके पास विधि बुलाया गया है, तो आप इसे कॉल करने में समर्थ नहीं होंगे!

प्रतिस्थापन

एड मिश्रण मिक्सिन में ओवरराइडिंग पद्धति में super आवश्यक रूप से इस प्रस्ताव में old के समान है।

redef कीवर्ड

उपर्युक्त के समान, लेकिन अधिलेखित विधि कॉल करने और def अकेला छोड़ने के लिए एक नया कीवर्ड जोड़ने के बजाय, हम तरीकों को रीडिफाई करने के लिए एक नया कीवर्ड जोड़ते हैं। यह पिछला संगत है, चूंकि सिंटैक्स वर्तमान में अवैध है:

 class Foo def bar 'Hello' end end class Foo redef bar old + ' World' end end Foo.new.bar # => 'Hello World' 

दो नए खोजशब्दों को जोड़ने के बजाय, हम redef अंदर super के अर्थ को फिर से परिभाषित कर सकते हैं:

 class Foo def bar 'Hello' end end class Foo redef bar super + ' World' end end Foo.new.bar # => 'Hello World' 

प्रतिस्थापन

एक विधि में redef में विधि को ओवरराइड करने के बराबर है। अतिप्रवाह विधि में super या इस प्रस्ताव में old तरह बर्ताव करती है।

अलियासिंग विधियों पर एक नज़र डालें, यह एक नए नाम की पद्धति का नाम बदलना है।

अधिक जानकारी के लिए और एक प्रारंभिक बिंदु इस विधि को बदलने की जगह पर ध्यान दें (विशेषकर पहला भाग)। रूबी एपीआई डॉक्स , यह भी (एक कम विस्तृत) उदाहरण प्रदान करता है।

क्लास जो ओवरराइड कर देगा, उस क्लास के बाद फिर से लोड किया जाना चाहिए जिसमें मूल विधि है, इसलिए उस फाइल में इसकी require है जो ओपरराइड करेगी।