Computer >> कंप्यूटर >  >> प्रोग्रामिंग >> Ruby

क्लास-लेवल इंस्टेंस वेरिएबल्स का जादू

पिछले रूबी मैजिक में, हमने यह पता लगाया था कि कैसे मॉड्यूल को मज़बूती से कक्षाओं में इंजेक्ट किया जाए, इसके .new को ओवरराइट करके विधि, हमें अतिरिक्त व्यवहार के साथ विधियों को लपेटने की अनुमति देती है।

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

पेश है Wrappable मॉड्यूल

ऑब्जेक्ट को इनिशियलाइज़ होने पर मॉड्यूल के साथ लपेटने के लिए, हमें क्लास को यह बताना होगा कि किस रैपिंग मॉडल का उपयोग करना है। आइए एक आसान Wrappable बनाकर शुरू करें मॉड्यूल जो एक wrap provides प्रदान करता है विधि जो दिए गए मॉड्यूल को एक वर्ग विशेषता के रूप में परिभाषित सरणी में धकेलती है। इसके अतिरिक्त, हम new . को फिर से परिभाषित करते हैं पिछली पोस्ट में चर्चा की गई विधि।

module Wrappable
  @@wrappers = []
 
  def wrap(mod)
    @@wrappers << mod
  end
 
  def new(*arguments, &block)
    instance = allocate
    @@wrappers.each { |mod| instance.singleton_class.include(mod) }
    instance.send(:initialize, *arguments, &block)
    instance
  end
end

किसी वर्ग में नया व्यवहार जोड़ने के लिए, हम extend . का उपयोग करते हैं . extend विधि दिए गए मॉड्यूल को कक्षा में जोड़ती है। विधियाँ तब वर्ग विधियाँ बन जाती हैं। इस वर्ग के उदाहरणों को लपेटने के लिए एक मॉड्यूल जोड़ने के लिए, अब हम wrap . को कॉल कर सकते हैं विधि।

module Logging
  def make_noise
    puts "Started making noise"
    super
    puts "Finished making noise"
  end
end
 
class Bird
  extend Wrappable
 
  wrap Logging
 
  def make_noise
    puts "Chirp, chirp!"
  end
end

आइए Bird . का एक नया उदाहरण बनाकर इसे आजमाएं और make_noise . को कॉल करना विधि।

bird = Bird.new
bird.make_noise
# Started making noise
# Chirp, chirp!
# Finished making noise

महान! यह अपेक्षा के अनुरूप काम करता है। हालांकि, एक बार जब हम Wrappable . के साथ दूसरी कक्षा का विस्तार करते हैं तो चीजें कुछ अजीब व्यवहार करने लगती हैं मॉड्यूल।

module Powered
  def make_noise
    puts "Powering up"
    super
    puts "Shutting down"
  end
end
 
class Machine
  extend Wrappable
 
  wrap Powered
 
  def make_noise
    puts "Buzzzzzz"
  end
end
 
machine = Machine.new
machine.make_noise
# Powering up
# Started making noise
# Buzzzzzz
# Finished making noise
# Shutting down
 
bird = Bird.new
bird.make_noise
# Powering up
# Started making noise
# Chirp, chirp!
# Finished making noise
# Shutting down

भले ही Machine Logging के साथ लपेटा नहीं गया है मॉड्यूल, यह अभी भी लॉगिंग जानकारी आउटपुट करता है। क्या बुरा है - यहां तक ​​​​कि पक्षी भी अब ऊपर और नीचे शक्ति दे रहा है। यह सही नहीं हो सकता, है ना?

इस समस्या की जड़ हम मॉड्यूल को स्टोर करने के तरीके में है। वर्ग चर @@wrappables Wrappable . पर परिभाषित किया गया है मॉड्यूल और जब भी हम एक नया मॉड्यूल जोड़ते हैं, उस वर्ग की परवाह किए बिना उपयोग किया जाता है जो wrap में प्रयोग किया जाता है।

Wrappable . पर परिभाषित वर्ग चरों को देखते समय यह और अधिक स्पष्ट हो जाता है मॉड्यूल और Bird और Machine कक्षाएं। जबकि Wrappable एक वर्ग विधि परिभाषित है, दो वर्ग नहीं।

Wrappable.class_variables # => [:@@wrappers]
Bird.class_variables # => []
Machine.class_variables # => []

इसे ठीक करने के लिए, हमें कार्यान्वयन को संशोधित करना होगा ताकि यह आवृत्ति चर का उपयोग करे। हालांकि, ये Bird . के उदाहरणों पर चर नहीं हैं या Machine , लेकिन इंस्टेंस वेरिएबल स्वयं कक्षाओं पर।

रूबी में, कक्षाएं केवल वस्तुएं होती हैं

यह निश्चित रूप से पहली बार में थोड़ा दिमागी दबदबा है, लेकिन फिर भी समझने के लिए एक बहुत ही महत्वपूर्ण अवधारणा है। कक्षाएं Class . के उदाहरण हैं और लेखन class Bird; end Bird = Class.new writing लिखने के बराबर है . चीजों को और अधिक भ्रमित करने के लिए Class Module . से इनहेरिट करता है जो Object . से इनहेरिट करता है . नतीजतन, कक्षाओं और मॉड्यूल में किसी भी अन्य वस्तु के समान तरीके होते हैं। अधिकांश विधियाँ जो हम कक्षाओं में उपयोग करते हैं (जैसे attr_accessor मैक्रो) वास्तव में Module . की उदाहरण विधियां हैं ।

कक्षाओं में इंस्टेंस वैरिएबल का उपयोग करना

आइए बदलते हैं Wrappable उदाहरण चर का उपयोग करने के लिए कार्यान्वयन। चीजों को थोड़ा साफ रखने के लिए, हम एक wrappers पेश करते हैं विधि जो या तो सरणी सेट करती है या मौजूदा एक को लौटाती है जब आवृत्ति चर पहले से मौजूद है। हम wrap . को भी संशोधित करते हैं और new तरीके ताकि वे उस नई विधि का उपयोग करें।

module Wrappable
  def wrap(mod)
    wrappers << mod
  end
 
  def wrappers
    @wrappers ||= []
  end
 
  def new(*arguments, &block)
    instance = allocate
    wrappers.each { |mod| instance.singleton_class.include(mod) }
    instance.send(:initialize, *arguments, &block)
    instance
  end
end

जब हम मॉड्यूल और दो वर्गों पर आवृत्ति चर की जांच करते हैं, तो हम देख सकते हैं कि दोनों Bird और Machine अब रैपिंग मॉड्यूल का अपना संग्रह बनाए रखें।

Wrappable.instance_variables #=> []
Bird.instance_variables #=> [:@wrappers]
Machine.instance_variables #=> [:@wrappers]

आश्चर्य नहीं कि यह उस समस्या को भी ठीक करता है जिसे हमने पहले देखा था - अब, दोनों वर्ग अपने-अपने मॉड्यूल के साथ लिपटे हुए हैं।

bird = Bird.new
bird.make_noise
# Started making noise
# Chirp, chirp!
# Finished making noise
 
machine = Machine.new
machine.make_noise
# Powering up
# Buzzzzzz
# Shutting down

विरासत का समर्थन करना

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

module Flying
  def make_noise
    super
    puts "Is flying away"
  end
end
 
class Pigeon < Bird
  wrap Flying
 
  def make_noise
    puts "Coo!"
  end
end
 
pigeon = Pigeon.new
pigeon.make_noise
# Coo!
# Is flying away

जैसा कि आप देख सकते हैं, यह अपेक्षा के अनुरूप काम नहीं करता, क्योंकि Pigeon रैपिंग मॉड्यूल का अपना संग्रह भी बनाए हुए है। हालांकि यह समझ में आता है कि Pigeon . के लिए परिभाषित रैपिंग मॉड्यूल Bird पर परिभाषित नहीं हैं , यह ठीक वैसा नहीं है जैसा हम चाहते हैं। आइए संपूर्ण वंशानुक्रम श्रृंखला से सभी रैपर प्राप्त करने का तरीका जानें।

हमारे लिए भाग्यशाली, रूबी Module#ancestors . प्रदान करता है एक वर्ग (या मॉड्यूल) से प्राप्त सभी वर्गों और मॉड्यूल को सूचीबद्ध करने की विधि।

Pigeon.ancestors # => [Pigeon, Bird, Object, Kernel, BasicObject]

एक grep जोड़कर कॉल करें, हम उन लोगों को चुन सकते हैं जो वास्तव में Wrappable . के साथ विस्तारित हैं . जैसा कि हम पहले श्रृंखला के ऊपर से रैपर के साथ उदाहरणों को लपेटना चाहते हैं, हम .reverse कहते हैं आदेश फ्लिप करने के लिए।

Pigeon.ancestors.grep(Wrappable).reverse # => [Bird, Pigeon]

रूबी का #=== विधि

रूबी का कुछ जादू #=== . में आता है (या मामले में समानता ) तरीका। डिफ़ॉल्ट रूप से, यह बिल्कुल #== . जैसा व्यवहार करता है (या समानता ) तरीका। हालांकि, कई वर्ग #=== . को ओवरराइड करते हैं case . में अलग व्यवहार प्रदान करने की विधि बयान। इस प्रकार आप रेगुलर एक्सप्रेशन का उपयोग कर सकते हैं (#=== #match? . के बराबर है ), या कक्षाएं (#=== #kind_of? . के बराबर है ) उन बयानों में। Enumerable#grep . जैसे तरीके , Enumerable#all? , या Enumerable#any? केस इक्वलिटी मेथड पर भी भरोसा करें।

अब हम flat_map(&:wrappers) . पर कॉल कर सकते हैं एक सरणी के रूप में विरासत श्रृंखला में परिभाषित सभी रैपरों की सूची प्राप्त करने के लिए।

Pigeon.ancestors.grep(Wrappable).reverse.flat_map(&:wrappers) # => [Logging]

जो कुछ बचा है वह उसे एक inherited_wrappers . में पैक कर रहा है मॉड्यूल और नई विधि को थोड़ा संशोधित करना ताकि वह wrappers . के बजाय इसका उपयोग करे विधि।

module Wrappable
  def inherited_wrappers
    ancestors
      .grep(Wrappable)
      .reverse
      .flat_map(&:wrappers)
  end
 
  def new(*arguments, &block)
    instance = allocate
    inherited_wrappers.each { |mod|instance.singleton_class.include(mod) }
    instance.send(:initialize, *arguments, &block)
    instance
  end
end

एक अंतिम परीक्षण रन पुष्टि करता है कि सब कुछ अब अपेक्षा के अनुरूप काम कर रहा है। रैपिंग मॉड्यूल केवल उस वर्ग (और उसके उपवर्गों) पर लागू होते हैं जिन पर वे लागू होते हैं।

bird = Bird.new
bird.make_noise
# Started making noise
# Chirp, chirp!
# Finished making noise
 
machine = Machine.new
machine.make_noise
# Powering up
# Buzzzzz
# Shutting down
 
pigeon = Pigeon.new
pigeon.make_noise
# Started making noise
# Coo!
# Finished making noise
# Is flying away

बस इतना ही!

बेशक, ये शोर करने वाले पक्षी एक सैद्धांतिक उदाहरण हैं (ट्वीट, ट्वीट)। लेकिन इनहेरिटेबल क्लास इंस्टेंस वेरिएबल यह समझने के लिए अच्छा नहीं है कि कक्षाएं कैसे काम करती हैं। वे एक महान उदाहरण हैं कि रूबी में कक्षाएं सिर्फ वस्तुएं हैं।

और हम स्वीकार करेंगे कि इनहेरिटेबल क्लास इंस्टेंस वेरिएबल वास्तविक जीवन में भी काफी उपयोगी हो सकते हैं। उदाहरण के लिए, बाद में आत्मनिरीक्षण करने की क्षमता वाले मॉडल पर विशेषताओं और संबंधों को परिभाषित करने के बारे में सोचें। हमारे लिए जादू यह है कि हम इसके साथ खेलें और इस बात की बेहतर समझ प्राप्त करें कि चीजें कैसे काम करती हैं। और समाधान के अगले स्तर के लिए अपना दिमाग खोलें। ‍♀️

हमेशा की तरह, हम यह सुनने के लिए उत्सुक हैं कि आप इस या इसी तरह के पैटर्न का उपयोग करके क्या बनाते हैं। बस ट्विटर पर @AppSignal पर चहकें।


  1. पायथन में वर्ग चर को परिभाषित करने का सही तरीका क्या है?

    वर्ग चर वे चर हैं जो __init__method के बाहर घोषित किए जाते हैं। ये स्थिर तत्व हैं, अर्थात्, वे वर्ग के उदाहरणों के बजाय वर्ग के हैं। ये वर्ग चर उस वर्ग के सभी उदाहरणों द्वारा साझा किए जाते हैं। वर्ग चर के लिए उदाहरण कोड उदाहरण class MyClass:   __item1 = 123   __item2 = "abc" &nb

  1. रूबीस्ट्स गाइड टू एनवायरनमेंट वेरिएबल्स

    यदि आप विकास और उत्पादन में वेब ऐप्स को प्रभावी ढंग से प्रबंधित करने में सक्षम होना चाहते हैं, तो आपको पर्यावरण चर को समझना होगा। यह हमेशा मामला नहीं था। कुछ साल पहले, शायद ही कोई अपने रेल ऐप्स को पर्यावरण चर के साथ कॉन्फ़िगर कर रहा था। लेकिन फिर हेरोकू हुआ। हरोकू ने डेवलपर्स को 12-कारक ऐप दृष्टिक

  1. लेक्सिकल स्कोपिंग और रूबी क्लास वेरिएबल्स

    रूबी के वर्ग चर भ्रमित कर रहे हैं। यहां तक ​​​​कि विशेषज्ञ रूबी उपयोगकर्ताओं को भी उन्हें समझना मुश्किल हो सकता है। सबसे स्पष्ट उदाहरण जड़त्व से संबंधित है: class Fruit @@kind = nil def self.kind @@kind end end class Apple < Fruit @@kind = apple end Apple.kind # => apple Fruit