पिछले रूबी मैजिक में, हमने यह पता लगाया कि .new मेथड को ओवरराइट करके मॉड्यूल्स को क्लासेस में कैसे विश्वसनीय रूप से इंजेक्ट किया जाए, जिससे हम मेथड्स को अतिरिक्त व्यवहार के साथ लपेट सकें।
इस बार, हम उस व्यवहार को अपने स्वयं के मॉड्यूल में निकालकर एक कदम आगे बढ़ा रहे हैं ताकि हम इसका पुन: उपयोग कर सकें। हम एक Wrappable मॉड्यूल बनाएंगे जो हमारे लिए क्लास एक्सटेंशन को संभालता है, और हम क्लास-लेवल इंस्टेंस वेरिएबल्स के बारे में सब कुछ सीखेंगे। चलिए सीधे इसमें गोता लगाते हैं!
Wrappable मॉड्यूल का परिचय
ऑब्जेक्ट्स को इनिशियलाइज़ेशन के समय मॉड्यूल्स के साथ लपेटने के लिए, हमें क्लास को यह बताना होगा कि किन रैपिंग मॉड्यूल्स का उपयोग करना है। आइए एक साधारण Wrappable मॉड्यूल बनाकर शुरू करें जो एक wrap मेथड प्रदान करता है जो दिए गए मॉड्यूल को क्लास एट्रिब्यूट के रूप में परिभाषित एक ऐरे में पुश करता है। इसके अलावा, हम पिछली पोस्ट में चर्चा के अनुसार 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 मॉड्यूल के साथ लपेटा नहीं गया है, फिर भी यह लॉगिंग जानकारी आउटपुट करता है। क्या बुरा है - अब बर्ड भी पावर अप और डाउन हो रही है। यह सही नहीं हो सकता, है ना?
इस समस्या की जड़ उस तरीके में निहित है जिससे हम मॉड्यूल्स को स्टोर कर रहे हैं। क्लास वेरिएबल @@wrappers 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 लिखने के बराबर है। चीजों को और अधिक भ्रमित करने के लिए 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 मॉड्यूल में पैक करना है और new मेथड को थोड़ा संशोधित करना है ताकि यह 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 पर ट्वीट करें।