EDIT : मुझे मूल रूप से इस उत्तर को लिखे हुए 9 साल हो गए हैं, और इसे चालू रखने के लिए कुछ कॉस्मेटिक सर्जरी के योग्य हैं।
आप यहाँ संपादित करने से पहले अंतिम संस्करण देख सकते हैं ।
आप नाम या कीवर्ड द्वारा अधिलेखित विधि को कॉल नहीं कर सकते । यह कई कारणों में से एक है कि क्यों बंदर पैचिंग से बचा जाना चाहिए और इसके बजाय वंशानुक्रम पसंद किया जाना चाहिए, क्योंकि जाहिर है कि आप ओवरराइड विधि कह सकते हैं ।
बन्दर पैचिंग से बचना
विरासत
इसलिए, यदि संभव हो तो, आपको इस तरह से कुछ पसंद करना चाहिए:
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हैं, उदाहरण के लिए क्योंकि वे एक ढांचे द्वारा बनाए जाते हैं जो आपके नियंत्रण से बाहर है (जैसेरूबी ऑन रेल्सउदाहरण के लिए), तो आप रैपर डिजाइन पैटर्न का उपयोग कर सकते हैं :
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 में लाइब्रेरी Object#DelegateClassसे सहायक विधि का उपयोग करता है delegate।
"साफ" बंदर पैचिंग
ऊपर दिए गए दो तरीकों से बन्दर पेटिंग से बचने के लिए सिस्टम को बदलने की आवश्यकता है। यह खंड बंदर पैचिंग के पसंदीदा और कम से कम आक्रामक तरीके को दिखाता है, सिस्टम को बदलना एक विकल्प नहीं होना चाहिए।
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आईएनजी के बजाय एक मिक्सिन prependआईएनजी:
class Foo
def bar
'Hello'
end
end
module FooExtensions
def bar
super + ' World'
end
end
class Foo
include FooExtensions
end
दुर्भाग्य से, यह काम नहीं करेगा। यह एक अच्छा विचार है, क्योंकि यह विरासत का उपयोग करता है, जिसका अर्थ है कि आप उपयोग कर सकते हैं super। हालांकि, Module#includemixin सम्मिलित करता है ऊपर वंशानुगत पदानुक्रम में वर्ग, जिसका अर्थ है कि 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बजाय इसका उपयोग कर रहे defहैं) को बंद कर देते हैं , तो यह (और केवल यह) अभी भी पहुंच से old_barबाहर हो जाएगा , इसके बाद भी।
संक्षिप्त विवरण:
old_bar = instance_method(:bar)
यहां हम barविधि को एक UnboundMethodऑब्जेक्ट ऑब्जेक्ट में लपेट रहे हैं और इसे स्थानीय चर पर असाइन कर रहे हैं old_bar। इसका मतलब है, अब हमारे पास एक तरीका है कि हम barइसे ओवरराइट करने के बाद भी पकड़ सकते हैं ।
old_bar.bind(self)
यह थोड़ा मुश्किल है। मूल रूप से, रूबी में (और बहुत अधिक सभी एकल-प्रेषण आधारित ओओ भाषाओं में), एक विधि एक विशिष्ट रिसीवर ऑब्जेक्ट से जुड़ी है, जिसे selfरूबी में कहा जाता है। दूसरे शब्दों में: एक विधि हमेशा जानती है कि उसे किस वस्तु पर बुलाया गया था, यह जानता है कि उसका क्या selfहै। लेकिन, हमने एक वर्ग से सीधे तरीके को पकड़ लिया, यह कैसे पता चलता है कि इसका क्या selfहै?
ठीक है, यदि ऐसा नहीं होता है, जिसके कारण हम की जरूरत है bindहमारे UnboundMethodएक वस्तु पहले है, जो एक के लिए वापस आ जाएगी Methodउद्देश्य यह है कि हम तो कह सकते हैं। ( UnboundMethodएस को नहीं बुलाया जा सकता है, क्योंकि वे नहीं जानते कि उनके बिना क्या करना है self।)
और हम bindइसे क्या करते हैं ? हम बस bindइसे अपने आप से करते हैं, इस तरह से यह बिल्कुल वैसा ही व्यवहार करेगा जैसे मूल में barहोगा!
अंत में, हमें Methodउस कॉल को वापस करने की आवश्यकता है bind। रूबी 1.9 में, उसके लिए कुछ निफ्टी नया सिंटैक्स है ( .()), लेकिन अगर आप 1.8 पर हैं, तो आप बस callविधि का उपयोग कर सकते हैं ; क्या है कि .()करने के लिए वैसे भी अनुवाद हो जाता है।
यहाँ कुछ अन्य प्रश्न दिए गए हैं, जहाँ उन कुछ अवधारणाओं को समझाया गया है:
"गंदा" बंदर पैचिंग
हमारे बंदर पेटिंग के साथ समस्या यह है कि जब हम विधि को अधिलेखित करते हैं, तो विधि चली जाती है, इसलिए हम इसे अब और नहीं कह सकते। तो, चलो बस एक बैकअप प्रतिलिपि बनाते हैं!
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विधि के साथ नाम स्थान को प्रदूषित कर दिया है । यह विधि हमारे प्रलेखन में दिखाई देगी, यह हमारे आईडीई में कोड पूरा होने में दिखाई देगी, यह प्रतिबिंब के दौरान दिखाई देगी। इसके अलावा, इसे अभी भी कहा जा सकता है, लेकिन संभवत: हमने इसे बंद कर दिया, क्योंकि हमें इसका व्यवहार पसंद नहीं था, इसलिए हम नहीं चाहते कि दूसरे लोग इसे बुला सकें।
इस तथ्य के बावजूद कि इसमें कुछ अवांछनीय गुण हैं, यह दुर्भाग्य से AciveSupport के माध्यम से लोकप्रिय हो गया है Module#alias_method_chain।
मामले में आपको केवल कुछ विशिष्ट स्थानों में भिन्न व्यवहार की आवश्यकता है और पूरे सिस्टम में नहीं, आप बंदर पैच को एक विशिष्ट दायरे तक सीमित करने के लिए रिफाइनमेंट का उपयोग कर सकते हैं। मैं इसे 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।
विधि संयोजन
एक विचार सीएलओएस से विधि संयोजन का विचार था। यह मूल रूप से आस्पेक्ट-ओरिएंटेड प्रोग्रामिंग के सबसेट का बहुत हल्का संस्करण है।
वाक्य रचना का उपयोग करना
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। शायद हम superकीवर्ड का उपयोग कर सकते हैं?
class Foo
def bar
'Hello'
end
end
class Foo
def bar:after
super + ' World'
end
end
प्रतिस्थापन
इससे पहले कि कॉम्बिनेटर एक मिश्रण के prependसाथ एक ओवरराइडिंग विधि के साथ आईएनजी के बराबर होता है जो विधि superके बहुत अंत में कॉल करता है। इसी तरह, Combinator के बाद के बराबर है prependएक अधिभावी विधि है कि कॉल के साथ एक mixin ing 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'
इसके साथ मुख्य समस्या यह है कि यह पीछे की ओर असंगत है: यदि आपके पास विधि कहा जाता है old, तो आप इसे कॉल नहीं कर पाएंगे!
प्रतिस्थापन
superएक prependएड मिक्सिन में ओवरराइडिंग विधि अनिवार्य रूप oldसे इस प्रस्ताव के समान है ।
redef कीवर्ड
उपरोक्त के समान, लेकिन ओवरराइट विधि को कॉल करने और defअकेले छोड़ने के लिए एक नया कीवर्ड जोड़ने के बजाय , हम नए कीवर्ड को नए सिरे से परिभाषित करने के तरीकों को जोड़ते हैं । यह पीछे की ओर संगत है, क्योंकि सिंटैक्स वर्तमान में वैसे भी अवैध है:
class Foo
def bar
'Hello'
end
end
class Foo
redef bar
old + ' World'
end
end
Foo.new.bar # => 'Hello World'
दो नए कीवर्ड जोड़ने के बजाय , हम superअंदर के अर्थ को फिर से परिभाषित कर सकते हैं redef:
class Foo
def bar
'Hello'
end
end
class Foo
redef bar
super + ' World'
end
end
Foo.new.bar # => 'Hello World'
प्रतिस्थापन
redefएक विधि का मिश्रण एक prependएड मिक्सिन में विधि को ओवरराइड करने के बराबर है । superओवरराइडिंग विधि में superया oldइस प्रस्ताव में व्यवहार होता है ।