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#include
mixin सम्मिलित करता है ऊपर वंशानुगत पदानुक्रम में वर्ग, जिसका अर्थ है कि 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
इस प्रस्ताव में व्यवहार होता है ।