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


443

कहो कि मैं एक कक्षा में एक विधि का पालन-पोषण कर रहा हूं, मैं ओवरराइडिंग विधि से ओवरराइड विधि को कैसे कह सकता हूं? Ie कुछ थोड़ा सा पसंद हैsuper

उदाहरण के लिए

class Foo
  def bar()
    "Hello"
  end
end 

class Foo
  def bar()
    super() + " World"
  end
end

>> Foo.new.bar == "Hello World"

क्या पहली फू क्लास कुछ दूसरी नहीं होनी चाहिए और दूसरी फू किस से विरासत में मिली होगी?
ड्रेको एटर

1
नहींं, मैं बंदर हूं। मैं उम्मीद कर रहा था कि सुपर जैसा कुछ होगा () मैं मूल विधि को कॉल करने के लिए उपयोग कर सकता हूं
जेम्स हॉलिंगवर्थ

1
इसकी आवश्यकता तब होती है जब आप इसके निर्माण Foo और उपयोग को नियंत्रित नहीं करते हैं Foo::bar। तो आपको विधि को बंदर करना होगा
हालिल Hal ज़गुर

जवाबों:


1165

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#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विधि का उपयोग कर सकते हैं ; क्या है कि .()करने के लिए वैसे भी अनुवाद हो जाता है।

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

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

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विधि के साथ नाम स्थान को प्रदूषित कर दिया है । यह विधि हमारे प्रलेखन में दिखाई देगी, यह हमारे आईडीई में कोड पूरा होने में दिखाई देगी, यह प्रतिबिंब के दौरान दिखाई देगी। इसके अलावा, इसे अभी भी कहा जा सकता है, लेकिन संभवत: हमने इसे बंद कर दिया, क्योंकि हमें इसका व्यवहार पसंद नहीं था, इसलिए हम नहीं चाहते कि दूसरे लोग इसे बुला सकें।

इस तथ्य के बावजूद कि इसमें कुछ अवांछनीय गुण हैं, यह दुर्भाग्य से 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इस प्रस्ताव में व्यवहार होता है ।


@ Jörg W Mittag, मेथड रैपिंग अप्रोच थ्रेड सेफ है? क्या होता है जब दो समवर्ती धागे bindएक ही old_methodचर पर कहते हैं ?
हरीश शेट्टी

1
@ कंदडाबोग्गु: मैं यह पता लगाने की कोशिश कर रहा हूं कि आपका वास्तव में क्या मतलब है :-) हालांकि, मुझे पूरा यकीन है कि यह रूबी में किसी भी अन्य प्रकार के मेटाप्रोग्रामिंग से कम धागा-सुरक्षित नहीं है। विशेष रूप से, हर कॉल UnboundMethod#bindएक नया, अलग लौटाएगा Method, इसलिए, मैं किसी भी संघर्ष को उत्पन्न होते नहीं देखता, चाहे आप इसे एक पंक्ति में दो बार या एक ही समय में दो बार अलग-अलग थ्रेड से कॉल करें।
जोर्ग डब्ल्यू मित्तग

1
जब से मैंने माणिक और रेल पर शुरुआत की है, तब से इस तरह पैचिंग पर एक स्पष्टीकरण की तलाश कर रहा था। बहुत बढ़िया जवाब! मेरे लिए केवल एक चीज गायब थी class_eval बनाम एक वर्ग को फिर से खोलने पर एक नोट। यहाँ यह है: stackoverflow.com/a/10304721/188462
यूजीन

1
रूबी 2.0 में शोधन ब्लॉग है ।wyeworks.com
2012/

5
आप कहां ढूंढूं oldऔर redef? मेरी 2.0.0 उनके पास नहीं है। आह, यह मुश्किल है कि अन्य प्रतिस्पर्धी विचारों
नकीलोन

12

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

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


-1

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

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.