रूबी के स्टैंडर्ड लाइब्रेरी में छिपे हुए रत्नों की आज की खोज में, हम डेलीगेशन को देखने जा रहे हैं।
दुर्भाग्य से, यह शब्द — कई अन्य की तरह — वर्षों में कुछ हद तक भ्रमित हो गया है और अलग-अलग लोगों के लिए अलग-अलग चीजें मायने रखता है। विकिपीडिया के अनुसार:
डेलीगेशन एक ऑब्जेक्ट (रिसीवर) के एक सदस्य (प्रॉपर्टी या विधि) का मूल्यांकन दूसरे मूल ऑब्जेक्ट (सेंडर) के संदर्भ में करने को संदर्भित करता है। डेलीगेशन स्पष्ट रूप से किया जा सकता है, सेंडिंग ऑब्जेक्ट को रिसीविंग ऑब्जेक्ट को पास करके, जिसे किसी भी ऑब्जेक्ट-ओरिएंटेड भाषा में किया जा सकता है; या निहित रूप से, भाषा के सदस्य लुकअप नियमों द्वारा, जिसके लिए भाषा समर्थन की आवश्यकता होती है।
हालांकि, अक्सर लोग इस शब्द का उपयोग एक ऑब्जेक्ट द्वारा खुद को आर्गुमेंट के रूप में पास किए बिना दूसरे ऑब्जेक्ट की संबंधित विधि को कॉल करने का वर्णन करने के लिए भी करते हैं, जिसे अधिक सटीक रूप से "फॉरवर्डिंग" कहा जा सकता है।
इसे ध्यान में रखते हुए, हम इस लेख के बाकी हिस्से के लिए इन दोनों पैटर्न का वर्णन करने के लिए "डेलीगेशन" का उपयोग करेंगे।
Delegator
आइए रूबी के स्टैंडर्ड लाइब्रेरी के Delegator क्लास को देखकर रूबी में डेलीगेशन की खोज शुरू करें जो कई डेलीगेशन पैटर्न प्रदान करता है।
SimpleDelegator
इनमें से सबसे आसान, और वह जिसे मैंने जंगल में सबसे अधिक देखा है, वह है SimpleDelegator, जो इनिशियलाइज़र के माध्यम से प्रदान किए गए ऑब्जेक्ट को रैप करता है और फिर सभी गायब विधियों को इसमें डेलीगेट करता है। आइए इसे क्रिया में देखें:
require 'delegate' User = Struct.new(:first_name, :last_name) class UserDecorator < SimpleDelegator def full_name "#{first_name} #{last_name}" end endसबसे पहले, हमें SimpleDelegator को अपने कोड में उपलब्ध कराने के लिए require 'delegate' की आवश्यकता थी। हमने first_name और last_name एक्सेसर्स के साथ एक साधारण User क्लास बनाने के लिए एक Struct का भी उपयोग किया। फिर हमने UserDecorator जोड़ा जो व्यक्तिगत नाम भागों को एक स्ट्रिंग में जोड़ने वाली full_name विधि को परिभाषित करता है। यहीं SimpleDelegator खेल में आता है: चूंकि न तो first_name और न ही last_name वर्तमान क्लास पर परिभाषित हैं, इसलिए उन्हें रैप्ड ऑब्जेक्ट पर कॉल किया जाएगा:
decorated_user = UserDecorator.new(User.new("John", "Doe")) decorated_user.full_name #=> "John Doe"SimpleDelegator हमें super के साथ डेलीगेटेड विधियों को ओवरराइड करने की भी अनुमति देता है, जो रैप्ड ऑब्जेक्ट पर संबंधित विधि को कॉल करता है। हम इसका उपयोग अपने उदाहरण में केवल पूर्ण प्रथम नाम के बजाय इनिशियल दिखाने के लिए कर सकते हैं:
class UserDecorator < SimpleDelegator def first_name "#{super[0]}." end enddecorated_user.first_name #=> "J." decorated_user.full_name #=> "J. Doe"Delegator
उपरोक्त उदाहरणों को पढ़ते समय, क्या आपने सोचा कि हमारे UserDecorator को कैसे पता चला कि किस ऑब्जेक्ट को डेलीगेट करना है? इसका उत्तर SimpleDelegator की पैरेंट क्लास — Delegator में निहित है। यह __getobj__ और __setobj__ के कार्यान्वयन प्रदान करके कस्टम डेलीगेशन योजनाओं को परिभाषित करने के लिए एक अमूर्त बेस क्लास है जो क्रमशः डेलीगेशन लक्ष्य को प्राप्त और सेट करते हैं। इस ज्ञान का उपयोग करके, हम प्रदर्शन उद्देश्यों के लिए SimpleDelegator का अपना संस्करण आसानी से बना सकते हैं:
class MyDelegator < Delegator attr_accessor :wrapped alias_method :__getobj__, :wrapped def initialize(obj) @wrapped = obj end end class UserDecorator < MyDelegator def full_name "#{first_name} #{last_name}" end endयह SimpleDelegator के वास्तविक कार्यान्वयन से थोड़ा भिन्न है जो अपने initialize विधि में __setobj__ को कॉल करता है। चूंकि हमारे कस्टम डेलीगेटर क्लास को इसकी आवश्यकता नहीं है, हमने उस विधि को पूरी तरह से छोड़ दिया।
यह बिल्कुल हमारे पिछले उदाहरण की तरह काम करना चाहिए; और वास्तव में ऐसा होता है:
UserDecorator.superclass #=> MyDelegator < Delegator decorated_user = UserDecorator.new(User.new("John", "Doe")) decorated_user.full_name #=> "John Doe"DelegateMethod
Delegate द्वारा हमारे लिए प्रदान किया गया अंतिम डेलीगेशन पैटर्न कुछ अजीब नाम वाला Object.DelegateClass विधि है। यह एक विशिष्ट क्लास के लिए एक डेलीगेटर क्लास उत्पन्न करता है और लौटाता है, जिससे हम इससे विरासत ले सकते हैं:
class MyClass < DelegateClass(ClassToDelegateTo) def initialize super(obj_of_ClassToDelegateTo) end endहालांकि यह पहली नज़र में भ्रमित लग सकता है — विशेष रूप से यह तथ्य कि विरासत का दायां भाग मनमाना रूबी कोड हो सकता है — यह वास्तव में उन पैटर्न का पालन करता है जिनका हमने पहले पता लगाया था, यानी यह SimpleDelegator से विरासत लेने के समान है।
रूबी का स्टैंडर्ड लाइब्रेरी अपनी Tempfile क्लास को परिभाषित करने के लिए इस सुविधा का उपयोग करता है जो अपना अधिकांश काम File क्लास को डेलीगेट करती है जबकि स्टोरेज स्थान और फाइल डिलीशन के संबंध में कुछ विशेष नियम स्थापित करती है। हम उसी तंत्र का उपयोग एक कस्टम Logfile क्लास को इस तरह सेट करने के लिए कर सकते हैं:
class Logfile < DelegateClass(File) MODE = File::WRONLY|File::CREAT|File::APPEND def initialize(basename, logdir = '/var/log') # logdir द्वारा निर्दिष्ट स्थान पर लॉगफाइल बनाएं path = File.join(logdir, basename) logfile = File.open(path, MODE, 0644) # यह Delegator की इनिशियलाइज़ विधि को कॉल करेगा, इसलिए इस बिंदु से नीचे # हम अपने Logfile इंस्टेंस पर File से किसी भी विधि को कॉल कर सकते हैं। super(logfile) end endForwardable
दिलचस्प रूप से, रूबी का स्टैंडर्ड लाइब्रेरी हमें Forwardable मॉड्यूल और इसकी def_delegator और def_delegators विधियों के रूप में डेलीगेशन के लिए एक और लाइब्रेरी प्रदान करता है।
आइए अपने मूल UserDecorator उदाहरण को Forwardable के साथ रीराइट करें।
require 'forwardable' User = Struct.new(:first_name, :last_name) class UserDecorator extend Forwardable def_delegators :@user, :first_name, :last_name def initialize(user) @user = user end def full_name "#{first_name} #{last_name}" end end decorated_user = UserDecorator.new(User.new("John", "Doe")) decorated_user.full_name #=> "John Doe"सबसे ध्यान देने योग्य अंतर यह है कि डेलीगेशन method_missing के माध्यम से स्वचालित रूप से प्रदान नहीं की जाती, बल्कि इसके बजाय, प्रत्येक विधि के लिए स्पष्ट रूप से घोषित करने की आवश्यकता होती है जिसे हम फॉरवर्ड करना चाहते हैं। यह हमें रैप्ड ऑब्जेक्ट की किसी भी विधि को "छिपाने" की अनुमति देता है जिसे हम अपने क्लाइंट्स को उजागर नहीं करना चाहते, जो हमारे पब्लिक इंटरफेस पर अधिक नियंत्रण देता है और मुख्य कारण है कि मैं आमतौर पर SimpleDelegator पर Forwardable को प्राथमिकता देता हूं।
Forwardable की एक और अच्छी विशेषता def_delegator के माध्यम से डेलीगेटेड विधियों का नाम बदलने की क्षमता है, जो एक वैकल्पिक तीसरा आर्गुमेंट स्वीकार करता है जो वांछित उपनाम को निर्दिष्ट करता है:
class UserDecorator extend Forwardable def_delegator :@user, :first_name, :personal_name def_delegator :@user, :last_name, :family_name def initialize(user) @user = user end def full_name "#{personal_name} #{family_name}" end endउपरोक्त UserDecorator केवल उपनामित personal_name और family_name विधियों को उजागर करता है, जबकि अभी भी रैप्ड User ऑब्जेक्ट के first_name और last_name पर फॉरवर्ड करता है:
decorated_user = UserDecorator.new(User.new("John", "Doe")) decorated_user.first_name #=> NoMethodError: undefined method `first_name' for #<UserDecorator:0x000000010f995cb8> decorated_user.personal_name #=> "John"यह सुविधा कई बार काफी काम आ सकती है। मैंने अतीत में समान इंटरफेस लेकिन विधि नामों के संबंध में अलग-अलग अपेक्षाओं वाली लाइब्रेरीज़ के बीच कोड माइग्रेट करने जैसी चीजों के लिए इसका सफलतापूर्वक उपयोग किया है।
स्टैंडर्ड लाइब्रेरी के बाहर
स्टैंडर्ड लाइब्रेरी में मौजूदा डेलीगेशन समाधानों के बावजूद, रूबी समुदाय ने वर्षों में कई विकल्प विकसित किए हैं और हम उनमें से दो का पता लगाएंगे।
delegate
रेल्स की लोकप्रियता को देखते हुए, इसकी delegate विधि रूबी डेवलपर्स द्वारा उपयोग किए जाने वाले डेलीगेशन का सबसे आम रूप हो सकती है। यहां बताया गया है कि हम इसका उपयोग अपने भरोसेमंद पुराने UserDecorator को रीराइट करने के लिए कैसे कर सकते हैं:
# एक वास्तविक रेल्स ऐप में यह सबसे अधिक संभावना ApplicationRecord का सबक्लास होगा User = Struct.new(:first_name, :last_name) class UserDecorator attr_reader :user delegate :first_name, :last_name, to: :user def initialize(user) @user = user end def full_name "#{first_name} #{last_name}" end end decorated_user = UserDecorator.new(User.new("John", "Doe")) decorated_user.full_name #=> "John Doe"यह Forwardable के समान है, लेकिन हमें extend का उपयोग करने की आवश्यकता नहीं है क्योंकि delegate सीधे Module पर परिभाषित है और इसलिए हर क्लास या मॉड्यूल बॉडी में उपलब्ध है (अच्छे या बुरे के लिए, आप तय करें)। हालांकि, delegate की आस्तीन में कुछ साफ-सुथरे ट्रिक्स हैं। सबसे पहले, :prefix विकल्प है जो डेलीगेटेड विधि नामों को उस ऑब्जेक्ट के नाम से प्रीफिक्स करेगा जिसे हम डेलीगेट कर रहे हैं। तो,
delegate :first_name, :last_name, to: :user, prefix: trueयह user_first_name और user_last_name विधियों को जेनरेट करेगा। वैकल्पिक रूप से हम एक कस्टम प्रीफिक्स प्रदान कर सकते हैं:
delegate :first_name, :last_name, to: :user, prefix: :account अब हम उपयोगकर्ता के नाम के विभिन्न भागों को account_first_name और account_last_name के रूप में एक्सेस कर सकते हैं।
delegate का एक और दिलचस्प विकल्प इसका :allow_nil विकल्प है। यदि हम जिस ऑब्जेक्ट को डेलीगेट करते हैं वह वर्तमान में nil है — उदाहरण के लिए एक अनसेट ActiveRecord रिलेशन के कारण — तो हम आमतौर पर NoMethodError के साथ समाप्त होंगे:
decorated_user = UserDecorator.new(nil) decorated_user.first_name #=> Module::DelegationError: UserDecorator#first_name delegated to @user.first_name, but @user is nilहालांकि, :allow_nil विकल्प के साथ, यह कॉल सफल होगी और इसके बजाय nil लौटाएगी:
class UserDecorator delegate :first_name, :last_name, to: :user, allow_nil: true ... end decorated_user = UserDecorator.new(nil) decorated_user.first_name #=> nilCasting
हम जिस अंतिम डेलीगेशन विकल्प को देखेंगे वह जिम गे का Casting जेम है, जो डेवलपर्स को "रूबी में विधियों को डेलीगेट करने और self को संरक्षित करने" की अनुमति देता है। यह शायद डेलीगेशन की सख्त परिभाषा के सबसे करीब है, क्योंकि यह रूबी की डायनामिक प्रकृति का उपयोग करके एक विधि कॉल के रिसीवर को अस्थायी रूप से रीबाइंड करता है, इसके समान:
UserDecorator.instance_method(:full_name).bind(user).call #=> "John Doe"इसका सबसे दिलचस्प पहलू यह है कि डेवलपर्स अपने सुपरक्लास पदानुक्रम को बदले बिना ऑब्जेक्ट्स में व्यवहार जोड़ सकते हैं।
require 'casting' User = Struct.new(:first_name, :last_name) module UserDecorator def full_name "#{first_name} #{last_name}" end end user = User.new("John", "Doe") user.extend(Casting::Client) user.delegate(:full_name, UserDecorator)यहां हमने user को Casting::Client के साथ एक्सटेंड किया, जो हमें delegate विधि तक पहुंच देता है। वैकल्पिक रूप से, हम User क्लास के अंदर include Casting::Client का उपयोग करके इस क्षमता को सभी इंस्टेंस को दे सकते थे।
इसके अतिरिक्त, Casting एक ब्लॉक के जीवनकाल के लिए या मैन्युअल रूप से फिर से हटाए जाने तक अस्थायी रूप से व्यवहार जोड़ने के विकल्प प्रदान करता है। इसके लिए काम करने के लिए, हमें पहले गायब विधियों के डेलीगेशन को सक्षम करने की आवश्यकता है:
user.delegate_missing_methodsएक ब्लॉक की अवधि के लिए व्यवहार जोड़ने के लिए, हम तब Casting की delegating क्लास विधि का उपयोग कर सकते हैं:
Casting.delegating(user => UserDecorator) do user.full_name #=> "John Doe" end user.full_name #NoMethodError: undefined method `full_name' for #<struct User first_name="John", last_name="Doe">वैकल्पिक रूप से, हम व्यवहार को तब तक जोड़ सकते हैं जब तक हम स्पष्ट रूप से uncast को फिर से कॉल नहीं करते:
user.cast_as(UserDecorator) user.full_name #=> "John Doe" user.uncast NoMethodError: undefined method `full_name' for #<struct User first_name="John", last_name="Doe">जबकि अन्य प्रस्तुत समाधानों की तुलना में थोड़ा अधिक जटिल, Casting बहुत नियंत्रण प्रदान करता है और जिम अपनी क्लीन रूबी बुक में इसके विभिन्न उपयोगों और अधिक को प्रदर्शित करता है।
सारांश
डेलीगेशन और मेथड फॉरवर्डिंग संबंधित ऑब्जेक्ट्स के बीच जिम्मेदारियों को विभाजित करने के लिए उपयोगी पैटर्न हैं। सादे रूबी प्रोजेक्ट्स में, दोनों Delegator और Forwardable का उपयोग किया जा सकता है, जबकि रेल्स कोड इसकी delegate विधि की ओर झुकता है। इस बात पर अधिकतम नियंत्रण के लिए कि क्या डेलीगेट किया गया है, Casting जेम एक उत्कृष्ट विकल्प है, हालांकि यह अन्य समाधानों की तुलना में थोड़ा अधिक जटिल है।
गेस्ट ऑथर माइकल कोहल का रूबी के साथ प्रेम संबंध 2003 के आसपास शुरू हुआ। उन्हें भाषा के बारे में लिखना और बोलना भी पसंद है और वे बैंकॉक.rb और रूबीकॉन्फ थाईलैंड का सह-आयोजन करते हैं।