पिछले रूबी मैजिक में, हमने यह पता लगाया था कि कैसे मॉड्यूल को मज़बूती से कक्षाओं में इंजेक्ट किया जाए, इसके .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 पर चहकें।