रूबी में मॉड्यूल / मिश्रण से कक्षा विधियों का इनहेरिट करना


95

यह ज्ञात है कि रूबी में, क्लास के तरीके विरासत में मिलते हैं:

class P
  def self.mm; puts 'abc' end
end
class Q < P; end
Q.mm # works

हालाँकि, यह मेरे लिए आश्चर्य की बात है कि यह मिश्रण के साथ काम नहीं करता है:

module M
  def self.mm; puts 'mixin' end
end
class N; include M end
M.mm # works
N.mm # does not work!

मुझे पता है कि # कस्टम विधि ऐसा कर सकती है:

module X; def mm; puts 'extender' end end
Y = Class.new.extend X
X.mm # works

लेकिन मैं एक मिक्सिन लिख रहा हूँ (या, बल्कि लिखना चाहूँगा) जिसमें दोनों उदाहरण विधियाँ और वर्ग विधियाँ हैं:

module Common
  def self.class_method; puts "class method here" end
  def instance_method; puts "instance method here" end
end

अब मैं यह करना चाहूंगा:

class A; include Common
  # custom part for A
end
class B; include Common
  # custom part for B
end

मैं ए, बी Commonमॉड्यूल से उदाहरण और वर्ग दोनों तरीकों का वारिस चाहता हूं । लेकिन, निश्चित रूप से, यह काम नहीं करता है। तो, क्या यह एक एकल मॉड्यूल से इस विरासत को बनाने का एक गुप्त तरीका नहीं है?

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



1
भेद के साथ, यहाँ, मुझे पता है कि यह संभव है - मैं इसे करने के कम से कम बदसूरत तरीके के लिए कह रहा हूं और उन कारणों के लिए जो भोले के काम नहीं करते हैं।
बोरिस स्टिटनिक

1
अधिक अनुभव के साथ, मुझे समझ में आया कि रूबी बहुत दूर तक प्रोग्रामर के इरादे का अनुमान लगा रही होगी यदि एक मॉड्यूल को भी शामिल किया जाए जिसमें मॉड्यूल विधियाँ शामिल करने वाले के सिंगलटन वर्ग में शामिल हों। ऐसा इसलिए है क्योंकि "मॉड्यूल तरीके" वास्तव में सिंगलटन विधियों के अलावा और कुछ नहीं हैं। सिंगलटन विधियों के लिए मॉड्यूल विशेष नहीं हैं, वे ऐसे नामस्थान होने के लिए विशेष हैं जहां विधियों और स्थिरांक को परिभाषित किया गया है। नेमस्पेस पूरी तरह से एक मॉड्यूल के सिंगलटन तरीकों से असंबंधित है, इसलिए वास्तव में सिंगलटन विधियों का वर्ग वंशानुक्रम मॉड्यूल में इसकी कमी की तुलना में अधिक आश्चर्यजनक है।
बोरिस स्टिटनिक

जवाबों:


171

एक सामान्य मुहावरा includedहुक का उपयोग करना और वहां से कक्षा विधियों को इंजेक्ट करना है।

module Foo
  def self.included base
    base.send :include, InstanceMethods
    base.extend ClassMethods
  end

  module InstanceMethods
    def bar1
      'bar1'
    end
  end

  module ClassMethods
    def bar2
      'bar2'
    end
  end
end

class Test
  include Foo
end

Test.new.bar1 # => "bar1"
Test.bar2 # => "bar2"

26
includeउदाहरण विधियाँ extendजोड़ता है , वर्ग विधियाँ जोड़ता है। यह इस तरह काम करता है। मुझे असंगतता नहीं
दिखती

1
मैं धीरे-धीरे इस तथ्य के साथ रख रहा हूं, कि आपका सुझाव उतना ही सुरुचिपूर्ण हो जितना इस समस्या का व्यावहारिक समाधान। लेकिन मैं इस कारण को जानना चाहूंगा कि कक्षाओं के साथ काम करने वाली कोई चीज मॉड्यूल के साथ काम नहीं करती है।
बोरिस स्टिटनिक

6
@BorisStitnicky इस जवाब पर भरोसा करें। यह रूबी में एक बहुत ही सामान्य मुहावरा है, जो आपके द्वारा पूछे गए उपयोग के मामले को ठीक से हल करता है और आपके द्वारा अनुभव किए गए कारणों को ठीक करता है। यह "अशुभ" लग सकता है, लेकिन यह आपकी सबसे अच्छी शर्त है। (यदि आप ऐसा अक्सर करते हैं, तो आप includedविधि परिभाषा को किसी अन्य मॉड्यूल में स्थानांतरित कर सकते हैं और THAT को अपने मुख्य मॉड्यूल में शामिल कर सकते हैं;)
Phrogz

2
"क्यों?" के रूप में अधिक अंतर्दृष्टि के लिए इस धागे को पढ़ें। ।
फ्रोग्ज़

2
@ सर्वेक्षा: मॉड्यूल को डमी क्लास में शामिल करें।
सर्जियो तुलेंत्सेव

47

यहाँ पूरी कहानी है, यह समझने के लिए आवश्यक मेटाप्रोग्रामिंग अवधारणाओं की व्याख्या करना कि मॉड्यूल समावेश रूबी में किस तरह काम करता है।

एक मॉड्यूल शामिल होने पर क्या होता है?

एक वर्ग में एक मॉड्यूल शामिल करना वर्ग के पूर्वजों के लिए मॉड्यूल जोड़ता है । आप किसी भी वर्ग या मॉड्यूल के पूर्वजों को इसकी ancestorsविधि कहकर देख सकते हैं :

module M
  def foo; "foo"; end
end

class C
  include M

  def bar; "bar"; end
end

C.ancestors
#=> [C, M, Object, Kernel, BasicObject]
#       ^ look, it's right here!

जब आप किसी उदाहरण पर एक विधि कहते हैं C, तो रूबी प्रदान की गई नाम के साथ एक इंस्टेंस विधि खोजने के लिए इस पूर्वजों की सूची के प्रत्येक आइटम को देखेंगे । जब से हम शामिल Mमें C, Mअब के एक पूर्वज है C, इसलिए जब हम फोन fooका एक उदाहरण पर C, रूबी में है कि विधि मिलेगा M:

C.new.foo
#=> "foo"

ध्यान दें कि समावेशन किसी भी उदाहरण या वर्ग विधियों को क्लास में कॉपी नहीं करता है - यह केवल क्लास में एक "नोट" जोड़ता है जिसे इसे शामिल मॉड्यूल में इंस्टेंस विधियों के लिए भी देखना चाहिए।

हमारे मॉड्यूल में "वर्ग" विधियों के बारे में क्या?

क्योंकि शामिल करने से केवल आवृत्ति के तरीके बदल जाते हैं, जिसमें एक वर्ग में एक मॉड्यूल शामिल है, केवल उस वर्ग पर अपनी आवृत्ति के तरीकों को उपलब्ध करता है। मॉड्यूल में "वर्ग" विधियां और अन्य घोषणाएं स्वचालित रूप से कक्षा में कॉपी नहीं की जाती हैं:

module M
  def instance_method
    "foo"
  end

  def self.class_method
    "bar"
  end
end

class C
  include M
end

M.class_method
#=> "bar"

C.new.instance_method
#=> "foo"

C.class_method
#=> NoMethodError: undefined method `class_method' for C:Class

रूबी वर्ग के तरीकों को कैसे लागू करती है?

रूबी में, कक्षाएं और मॉड्यूल सादे वस्तु हैं - वे वर्ग के उदाहरण हैं Classऔर Module। इसका मतलब है कि आप गतिशील रूप से नई कक्षाएं बना सकते हैं, उन्हें चर आदि के लिए असाइन कर सकते हैं।

klass = Class.new do
  def foo
    "foo"
  end
end
#=> #<Class:0x2b613d0>

klass.new.foo
#=> "foo"

रूबी में भी, आपको वस्तुओं पर तथाकथित सिंगलटन विधियों को परिभाषित करने की संभावना है । इन विधियों को वस्तु के विशेष, छिपे हुए एकल वर्ग में नई आवृत्ति विधियों के रूप में जोड़ा जाता है :

obj = Object.new

# define singleton method
def obj.foo
  "foo"
end

# here is our singleton method, on the singleton class of `obj`:
obj.singleton_class.instance_methods(false)
#=> [:foo]

लेकिन कक्षाओं और मॉड्यूल सिर्फ सादे वस्तुओं के रूप में अच्छी तरह से नहीं कर रहे हैं? वास्तव में वे हैं! क्या इसका मतलब यह है कि उनके पास एकल तरीके भी हो सकते हैं? हाँ यह करता है! और इस तरह से क्लास तरीके पैदा होते हैं:

class Abc
end

# define singleton method
def Abc.foo
  "foo"
end

Abc.singleton_class.instance_methods(false)
#=> [:foo]

या, वर्ग विधि को परिभाषित करने का अधिक सामान्य तरीका selfवर्ग परिभाषा ब्लॉक के भीतर उपयोग करना है, जो वर्ग ऑब्जेक्ट को बनाए जाने के लिए संदर्भित करता है:

class Abc
  def self.foo
    "foo"
  end
end

Abc.singleton_class.instance_methods(false)
#=> [:foo]

मैं एक मॉड्यूल में वर्ग विधियों को कैसे शामिल करूं?

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

module M
  def new_instance_method; "hi"; end

  module ClassMethods
    def new_class_method; "hello"; end
  end
end

class HostKlass
  include M
  self.singleton_class.include M::ClassMethods
end

HostKlass.new_class_method
#=> "hello"

यह self.singleton_class.include M::ClassMethodsरेखा बहुत अच्छी नहीं लगती है, इसलिए रूबी ने जोड़ा Object#extend, जो ऐसा ही करता है - यानी ऑब्जेक्ट के सिंगलटन वर्ग में एक मॉड्यूल शामिल है:

class HostKlass
  include M
  extend M::ClassMethods
end

HostKlass.singleton_class.included_modules
#=> [M::ClassMethods, Kernel]
#    ^ there it is!

extendमॉड्यूल में कॉल को स्थानांतरित करना

यह पिछला उदाहरण दो कारणों से अच्छी तरह से संरचित कोड नहीं है:

  1. अब हम कॉल करनी होगी दोनों include और extendमें HostClassपरिभाषा हमारे मॉड्यूल ठीक से शामिल पाने के लिए। यह बहुत ही बोझिल हो सकता है अगर आपको बहुत सारे समान मॉड्यूल शामिल करने हों।
  2. HostClassसीधे संदर्भ M::ClassMethodsहै, जो एक है कार्यान्वयन विस्तार मॉड्यूल के Mकि HostClassपता है या के बारे में परवाह करने की जरूरत नहीं होनी चाहिए।

तो इस बारे में कैसे: जब हम includeपहली पंक्ति पर कॉल करते हैं, तो हम किसी तरह मॉड्यूल को सूचित करते हैं कि इसे शामिल किया गया है, और इसे हमारी कक्षा की वस्तु भी दें, ताकि यह extendस्वयं को कॉल कर सके। इस तरह, यह क्लास के तरीकों को जोड़ने के लिए मॉड्यूल का काम है अगर वह चाहता है।

यह वही है जो विशेष self.includedविधि के लिए है। जब भी मॉड्यूल को किसी अन्य वर्ग (या मॉड्यूल) में शामिल किया जाता है, रूबी स्वचालित रूप से इस विधि को कॉल करती है, और पहले तर्क के रूप में होस्ट क्लास ऑब्जेक्ट में पास होती है:

module M
  def new_instance_method; "hi"; end

  def self.included(base)  # `base` is `HostClass` in our case
    base.extend ClassMethods
  end

  module ClassMethods
    def new_class_method; "hello"; end
  end
end

class HostKlass
  include M

  def self.existing_class_method; "cool"; end
end

HostKlass.singleton_class.included_modules
#=> [M::ClassMethods, Kernel]
#    ^ still there!

बेशक, कक्षा के तरीकों को जोड़ना केवल एक चीज नहीं है जिसे हम अंदर कर सकते हैं self.included। हमारे पास क्लास ऑब्जेक्ट है, इसलिए हम उस पर किसी अन्य (क्लास) विधि को कॉल कर सकते हैं:

def self.included(base)  # `base` is `HostClass` in our case
  base.existing_class_method
  #=> "cool"
end

2
अद्भुत जवाब! आखिरकार संघर्ष के एक दिन बाद अवधारणा को समझने में सक्षम था। धन्यवाद।
संकल्प

1
मुझे लगता है कि यह सबसे अच्छा लिखित उत्तर हो सकता है जो मैंने SO पर देखा है। अविश्वसनीय स्पष्टता के लिए और रूबी के मेरी निस्संदेहता को बढ़ाने के लिए धन्यवाद। अगर मैं यह एक 100pt बोनस मैं उपहार सकता है!
पीटर निक्सी

7

जैसा कि सर्जियो ने टिप्पणियों में उल्लेख किया है, उन लोगों के लिए जो पहले से ही रेल में हैं (या सक्रिय समर्थन के आधार पर बुरा नहीं मानते ), Concernयहाँ उपयोगी है:

require 'active_support/concern'

module Common
  extend ActiveSupport::Concern

  def instance_method
    puts "instance method here"
  end

  class_methods do
    def class_method
      puts "class method here"
    end
  end
end

class A
  include Common
end

3

आप अपना केक ले सकते हैं और ऐसा करके भी खा सकते हैं:

module M
  def self.included(base)
    base.class_eval do # do anything you would do at class level
      def self.doit #class method
        @@fred = "Flintstone"
        "class method doit called"
      end # class method define
      def doit(str) #instance method
        @@common_var = "all instances"
        @instance_var = str
        "instance method doit called"
      end
      def get_them
        [@@common_var,@instance_var,@@fred]
      end
    end # class_eval
  end # included
end # module

class F; end
F.include M

F.doit  # >> "class method doit called"
a = F.new
b = F.new
a.doit("Yo") # "instance method doit called"
b.doit("Ho") # "instance method doit called"
a.get_them # >> ["all instances", "Yo", "Flintstone"]
b.get_them # >> ["all instances", "Ho", "Flintstone"]

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


कुछ विषम चीजें हैं जो क्लास_एवल को पास करते समय काम नहीं करती हैं, जैसे स्थिरांक को परिभाषित करना, नेस्टेड कक्षाओं को परिभाषित करना, और विधियों के बाहर वर्ग चर का उपयोग करना। इन चीजों का समर्थन करने के लिए, आप एक ब्लॉक के बजाय class_eval को एक heredoc (स्ट्रिंग) दे सकते हैं: base.class_eval << - 'END'
पॉल डोनोह्यू
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.