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

विन्यास योग्य रूबी मॉड्यूल:मॉड्यूल बिल्डर पैटर्न

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

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

<ब्लॉकक्वॉट>

रूबी, नेमस्पेसिंग और मिक्सिन कार्यक्षमता में मॉड्यूल दो उद्देश्यों की पूर्ति करते हैं।

रेल ने कुछ वाक्यात्मक चीनी को ActiveSupport::Concern के रूप में जोड़ा , लेकिन सामान्य सिद्धांत वही रहता है।

समस्या

मिश्रित कार्यक्षमता प्रदान करने के लिए मॉड्यूल का उपयोग करना आमतौर पर सीधा होता है। हमें बस कुछ तरीकों को बंडल करना है और हमारे मॉड्यूल को कहीं और शामिल करना है:

module HelloWorld
  def hello
    "Hello, world!"
  end
end
class Test
  include HelloWorld
end
Test.new.hello
#=> "Hello, world!"

यह एक बहुत ही स्थिर तंत्र है, हालांकि रूबी की inherited और extended हुक विधियाँ शामिल वर्ग के आधार पर कुछ भिन्न व्यवहार की अनुमति देती हैं:

module HelloWorld
  def self.included(base)
    define_method :hello do
      "Hello, world from #{base}!"
    end
  end
end
class Test
  include HelloWorld
end
Test.new.hello
#=> "Hello, world from Test!"

यह कुछ अधिक गतिशील है लेकिन फिर भी हमारे कोड उपयोगकर्ताओं को, उदाहरण के लिए, hello का नाम बदलने की अनुमति नहीं देता है मॉड्यूल समावेशन समय पर विधि।

समाधान:कॉन्फ़िगर करने योग्य रूबी मॉड्यूल

पिछले कुछ वर्षों में, एक नया पैटर्न उभरा है जो इस समस्या को हल करता है, जिसे लोग कभी-कभी "मॉड्यूल बिल्डर पैटर्न" के रूप में संदर्भित करते हैं। यह तकनीक रूबी की दो प्राथमिक विशेषताओं पर निर्भर करती है:

  • मॉड्यूल किसी भी अन्य ऑब्जेक्ट की तरह ही होते हैं—उन्हें फ्लाई पर बनाया जा सकता है, वैरिएबल को असाइन किया जा सकता है, गतिशील रूप से संशोधित किया जा सकता है, साथ ही साथ पास किया जा सकता है या विधियों से लौटाया जा सकता है।

    def make_module
      # create a module on the fly and assign it to variable
      mod = Module.new
     
      # modify module
      mod.module_eval do
        def hello
          "Hello, AppSignal world!"
        end
      end
     
      # explicitly return it
      mod
    end
  • include . का तर्क या extend कॉल का मॉड्यूल होना जरूरी नहीं है, यह एक वापसी करने वाली अभिव्यक्ति भी हो सकती है, उदा। एक विधि कॉल।

    class Test
      # include the module returned by make_module
      include make_module
    end
     
    Test.new.hello
    #=> "Hello, AppSignal world!"

कार्य में मॉड्यूल निर्माता

अब हम इस ज्ञान का उपयोग Wrapper . नामक एक साधारण मॉड्यूल बनाने के लिए करेंगे , जो निम्नलिखित व्यवहार को लागू करता है:

  1. एक वर्ग जिसमें Wrapper शामिल है केवल एक विशिष्ट प्रकार की वस्तुओं को लपेट सकता है। कंस्ट्रक्टर तर्क प्रकार को सत्यापित करेगा और यदि प्रकार अपेक्षित से मेल नहीं खाता है तो एक त्रुटि उत्पन्न करेगा।
  2. लिपटे हुए ऑब्जेक्ट original_<class> . नामक इंस्टेंस विधि के माध्यम से उपलब्ध होंगे , जैसे original_integer या original_string
  3. यह हमारे कोड के उपभोक्ताओं को इस एक्सेसर विधि के लिए एक वैकल्पिक नाम निर्दिष्ट करने की अनुमति देगा, उदाहरण के लिए, the_string

आइए एक नजर डालते हैं कि हम चाहते हैं कि हमारा कोड कैसा व्यवहार करे:

# 1
class IntWrapper
 # 2
 include Wrapper.for(Integer)
end
 
# 3
i = IntWrapper.new(42)
i.original_integer
#=> 42
 
# 4
i = IntWrapper.new("42")
#=> TypeError (not a Integer)
 
# 5
class StringWrapper
 include Wrapper.for(String, accessor_name: :the_string)
end
 
s = StringWrapper.new("Hello, World!")
# 6
s.the_string
#=> "Hello, World!"

चरण 1 में, हम IntWrapper . नामक एक नए वर्ग को परिभाषित करते हैं ।

चरण 2 में, हम सुनिश्चित करते हैं कि इस वर्ग में केवल नाम से एक मॉड्यूल शामिल नहीं है, बल्कि इसके बजाय, Wrapper.for(Integer) पर कॉल के परिणाम में मिलाता है। ।

चरण 3 में, हम अपने नए वर्ग के किसी ऑब्जेक्ट को इंस्टेंट करते हैं और उसे i . को असाइन करते हैं . जैसा कि निर्दिष्ट किया गया है, इस ऑब्जेक्ट में original_integer . नामक एक विधि है , जो हमारी आवश्यकताओं में से एक को पूरा करता है।

चरण 4 में, यदि हम गलत प्रकार के तर्क को पारित करने का प्रयास करते हैं, जैसे कि एक स्ट्रिंग, एक उपयोगी TypeError उठाया जाएगा। अंत में, आइए सत्यापित करें कि उपयोगकर्ता कस्टम एक्सेसर नाम निर्दिष्ट करने में सक्षम हैं।

इसके लिए, हम StringWrapper . नामक एक नए वर्ग को परिभाषित करते हैं चरण 5 में और the_string pass पास करें कीवर्ड तर्क के रूप में accessor_name , जिसे हम चरण 6 में क्रिया में देखते हैं।

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

पहला प्रयास

आवश्यकताओं और उपयोग के उदाहरण के आधार पर, अब हम कार्यान्वयन शुरू कर सकते हैं। हम पहले से ही जानते हैं कि हमें Wrapper . नामक एक मॉड्यूल की आवश्यकता है for . नामक मॉड्यूल-स्तरीय विधि के साथ , जो एक वैकल्पिक कीवर्ड तर्क के रूप में एक वर्ग लेता है:

module Wrapper
 def self.for(klass, accessor_name: nil)
 end
end

चूंकि इस पद्धति का वापसी मूल्य include . का तर्क बन जाता है , यह एक मॉड्यूल होना चाहिए। इस प्रकार, हम Module.new . के साथ एक नया अनाम बना सकते हैं ।

Module.new do
end

हमारी आवश्यकताओं के अनुसार, इसे एक ऐसे कंस्ट्रक्टर को परिभाषित करने की आवश्यकता है जो पास-इन ऑब्जेक्ट के प्रकार के साथ-साथ एक उपयुक्त नामित एक्सेसर विधि की पुष्टि करता है। आइए कंस्ट्रक्टर से शुरू करें:

define_method :initialize do |object|
 raise TypeError, "not a #{klass}" unless object.is_a?(klass)
 @object = object
end

कोड का यह भाग define_method . का उपयोग करता है रिसीवर को गतिशील रूप से एक इंस्टेंस विधि जोड़ने के लिए। चूंकि ब्लॉक एक क्लोजर के रूप में कार्य करता है, यह klass . का उपयोग कर सकता है आवश्यक प्रकार की जांच करने के लिए बाहरी दायरे से वस्तु।

उचित नामित एक्सेसर विधि जोड़ना इतना कठिन नहीं है:

# 1
method_name = accessor_name || begin
 klass_name = klass.to_s.gsub(/(.)([A-Z])/,'\1_\2').downcase
 "original_#{klass_name}"
end
 
# 2
define_method(method_name) { @object }

सबसे पहले, हमें यह देखना होगा कि हमारे कोड का कॉलर accessor_name . में पास हुआ है या नहीं . अगर ऐसा है, तो हम इसे केवल method_name . पर असाइन करते हैं और फिर किया जाता है। अन्यथा, हम कक्षा लेते हैं और इसे एक अंडरस्कोर स्ट्रिंग में परिवर्तित करते हैं, उदाहरण के लिए, Integer integer . में बदल जाता है या OpenStruct open_struct . में . यह klass_name इसके बाद वेरिएबल को original_ . के साथ प्रीफिक्स किया जाता है अंतिम एक्सेसर नाम उत्पन्न करने के लिए। एक बार जब हम विधि का नाम जान लेते हैं, तो हम फिर से define_method . का उपयोग करते हैं इसे हमारे मॉड्यूल में जोड़ने के लिए, जैसा कि चरण 2 में दिखाया गया है।

यहाँ इस बिंदु तक का पूरा कोड है। एक लचीले और विन्यास योग्य रूबी मॉड्यूल के लिए 20 से कम लाइनें; बहुत बुरा नहीं है।

module Wrapper
  def self.for(klass, accessor_name: nil)
    Module.new do
      define_method :initialize do |object|
        raise TypeError, "not a #{klass}" unless object.is_a?(klass)
        @object = object
      end
 
      method_name = accessor_name || begin
        klass_name = klass.to_s.gsub(/(.)([A-Z])/,'\1_\2').downcase
        "original_#{klass_name}"
      end
 
      define_method(method_name) { @object }
    end
  end
end

चौकस पाठकों को याद हो सकता है कि Wrapper.for एक अनाम मॉड्यूल देता है। यह कोई समस्या नहीं है, लेकिन किसी वस्तु की विरासत श्रृंखला की जांच करते समय थोड़ा भ्रमित हो सकता है:

StringWrapper.ancestors
#=> [StringWrapper, #<Module:0x0000000107283680>, Object, Kernel, BasicObject]

यहां #<Module:0x0000000107283680> (यदि आप अनुसरण कर रहे हैं तो नाम अलग-अलग होगा) हमारे अनाम मॉड्यूल को संदर्भित करता है।

बेहतर संस्करण

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

module Wrapper
  def self.for(klass, accessor_name: nil)
    # 1
    mod = const_set("#{klass}InstanceMethods", Module.new)
 
    # 2
    mod.module_eval do
      define_method :initialize do |object|
        raise TypeError, "not a #{klass}" unless object.is_a?(klass)
        @object = object
      end
 
      method_name = accessor_name || begin
        klass_name = klass.to_s.gsub(/(.)([A-Z])/, '\1_\2').downcase
        "original_#{klass_name}"
      end
 
      define_method(method_name) { @object }
    end
 
    # 3
    mod
  end
end

पहले चरण में, हम "#{klass}InstanceMethods" नामक एक नेस्टेड मॉड्यूल बनाते हैं (उदाहरण के लिए IntegerInstanceMethods ), यह सिर्फ एक "खाली" मॉड्यूल है।

जैसा कि चरण 2 में दिखाया गया है, हम module_eval . का उपयोग करते हैं for . में विधि, जो उस मॉड्यूल के संदर्भ में कोड के एक ब्लॉक का मूल्यांकन करती है जिसे इसे कहा जाता है। इस तरह, हम चरण 3 में इसे वापस करने से पहले मॉड्यूल में व्यवहार जोड़ सकते हैं।

यदि अब हम Wrapper . सहित किसी वर्ग के पूर्वजों की जांच करते हैं , आउटपुट में ठीक से नामित मॉड्यूल शामिल होगा, जो पिछले अनाम मॉड्यूल की तुलना में बहुत अधिक अर्थपूर्ण और डीबग करने में आसान है।

StringWrapper.ancestors
#=> [StringWrapper, Wrapper::StringInstanceMethods, Object, Kernel, BasicObject]

जंगली में मॉड्यूल बिल्डर पैटर्न

इस पोस्ट के अलावा, हम मॉड्यूल बिल्डर पैटर्न या इसी तरह की तकनीकों को और कहां पा सकते हैं?

एक उदाहरण है dry-rb रत्नों का परिवार, जहां, उदाहरण के लिए, dry-effects विभिन्न प्रभाव संचालकों को कॉन्फ़िगरेशन विकल्प पास करने के लिए मॉड्यूल बिल्डरों का उपयोग करता है:

# This adds a `counter` effect provider. It will handle (eliminate) effects
include Dry::Effects::Handler.State(:counter)
 
# Providing scope is required
# All cache values will be scoped with this key
include Dry::Effects::Handler.Cache(:blog)

हम उत्कृष्ट श्राइन रत्न में समान उपयोग पा सकते हैं, जो रूबी अनुप्रयोगों के लिए एक फ़ाइल अपलोड टूलकिट प्रदान करता है:

class Photo < Sequel::Model
  include Shrine::Attachment(:image)
end

यह पैटर्न अभी भी अपेक्षाकृत नया है, लेकिन मुझे उम्मीद है कि हम भविष्य में इसे और अधिक देखेंगे, विशेष रूप से रत्नों में जो शुद्ध रूबी अनुप्रयोगों पर रेल वाले की तुलना में अधिक ध्यान केंद्रित करते हैं।

सारांश

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

पी.एस. यदि आप रूबी मैजिक की पोस्ट प्रेस से बाहर होते ही पढ़ना चाहते हैं, तो हमारे रूबी मैजिक न्यूजलेटर की सदस्यता लें और एक भी पोस्ट मिस न करें!


  1. रूबी में एक प्रोग्रामिंग भाषा का निर्माण:पार्सर

    Github पर पूर्ण स्रोत स्टॉफ़ल प्रोग्रामिंग भाषा का पूर्ण कार्यान्वयन GitHub पर उपलब्ध है। कोड को पढ़ने को आसान बनाने में मदद करने के लिए इस संदर्भ कार्यान्वयन में बहुत सारी टिप्पणियाँ हैं। अगर आपको बग मिलते हैं या आपके कोई प्रश्न हैं, तो बेझिझक कोई समस्या खोलें। इस ब्लॉग पोस्ट में, हम स्टॉफ़ल के

  1. रूबी में डेकोरेटर डिजाइन पैटर्न

    डेकोरेटर डिजाइन पैटर्न क्या है? और आप अपने रूबी प्रोजेक्ट्स में इस पैटर्न का उपयोग कैसे कर सकते हैं? डेकोरेटर डिज़ाइन पैटर्न नई क्षमताओं . जोड़कर किसी ऑब्जेक्ट को बेहतर बनाने में आपकी सहायता करता है इसमें बिना कक्षा बदले। आइए एक उदाहरण देखें! लॉगिंग और प्रदर्शन इस उदाहरण में हम रेस्ट-क्लाइंट जैस

  1. रूबी एन्यूमरेबल मॉड्यूल के लिए एक बुनियादी गाइड (+ मेरी पसंदीदा विधि)

    एन्यूमरेबल क्या है? एन्यूमरेबल पुनरावृत्ति विधियों का संग्रह . है , एक रूबी मॉड्यूल, और जो रूबी को एक महान प्रोग्रामिंग भाषा बनाता है उसका एक बड़ा हिस्सा। एन्यूमरेबल में उपयोगी तरीके शामिल हैं जैसे : map select inject असंख्य तरीके उन्हें एक ब्लॉक देकर काम करते हैं। उस ब्लॉक में आप उन्हें बत