रूबी के मानक पुस्तकालय में छिपे हुए रत्नों की आज की खोज में, हम प्रतिनिधिमंडल को देखने जा रहे हैं।
दुर्भाग्य से, यह शब्द - कई अन्य लोगों की तरह - पिछले कुछ वर्षों में कुछ गड़बड़ हो गया है और अलग-अलग लोगों के लिए अलग-अलग चीजें हैं। विकिपीडिया के अनुसार:
<ब्लॉकक्वॉट>प्रतिनिधिमंडल का तात्पर्य किसी अन्य मूल वस्तु (प्रेषक) के संदर्भ में एक वस्तु (प्राप्तकर्ता) के सदस्य (संपत्ति या विधि) का मूल्यांकन करना है। प्रत्यायोजन स्पष्ट रूप से प्राप्त करने वाली वस्तु को भेजने वाली वस्तु को पारित करके किया जा सकता है, जो किसी भी वस्तु-उन्मुख भाषा में किया जा सकता है; या परोक्ष रूप से, भाषा के सदस्य लुकअप नियमों द्वारा, जिसके लिए सुविधा के लिए भाषा समर्थन की आवश्यकता होती है।
हालांकि, अधिकतर लोग इस शब्द का उपयोग किसी वस्तु का वर्णन करने के लिए भी करते हैं, जो किसी अन्य वस्तु की संगत विधि को तर्क के रूप में पारित किए बिना कहते हैं, जिसे अधिक सटीक रूप से "अग्रेषण" के रूप में संदर्भित किया जा सकता है।
इसके साथ ही, हम शेष लेख के लिए इन दोनों प्रतिरूपों का वर्णन करने के लिए "प्रतिनिधिमंडल" का उपयोग करेंगे।
प्रतिनिधि
आइए मानक पुस्तकालय के Delegator
. को देखकर रूबी में प्रतिनिधिमंडल की खोज शुरू करें क्लास जो कई डेलिगेशन पैटर्न प्रदान करती है।
सरल प्रतिनिधि
इनमें से सबसे आसान, और जिसका मैंने जंगल में सबसे अधिक सामना किया है, वह है SimpleDelegator
, जो प्रारंभकर्ता के माध्यम से प्रदान की गई वस्तु को लपेटता है और उसके बाद सभी अनुपलब्ध विधियों को दर्शाता है। आइए इसे क्रिया में देखें:
require 'delegate'
User = Struct.new(:first_name, :last_name)
class UserDecorator < SimpleDelegator
def full_name
"#{first_name} #{last_name}"
end
end
सबसे पहले, हमें require 'delegate'
SimpleDelegator
. बनाने के लिए हमारे कोड के लिए उपलब्ध है। हमने एक Struct
. का भी उपयोग किया है एक साधारण User
बनाने के लिए first_name
. के साथ क्लास और last_name
एक्सेसर्स फिर हमने 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
end
decorated_user.first_name
#=> "J."
decorated_user.full_name
#=> "J. Doe"
प्रतिनिधि
ऊपर दिए गए उदाहरणों को पढ़ते हुए, क्या आपने सोचा कि हमारा 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
. से थोड़ा अलग है का वास्तविक कार्यान्वयन जो __setobj__
. को कॉल करता है इसके initialize
. में तरीका। चूंकि हमारे कस्टम प्रतिनिधि वर्ग को इसकी कोई आवश्यकता नहीं है, इसलिए हमने उस पद्धति को पूरी तरह से छोड़ दिया है।
यह बिल्कुल हमारे पिछले उदाहरण की तरह काम करना चाहिए; और वास्तव में यह करता है:
UserDecorator.superclass
#=> MyDelegator < Delegator
decorated_user = UserDecorator.new(User.new("John", "Doe"))
decorated_user.full_name
#=> "John Doe"
प्रतिनिधि विधि
अंतिम प्रतिनिधिमंडल पैटर्न Delegator
हमारे लिए प्रदान करता है कुछ हद तक अजीब नाम है 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')
# Create logfile in location specified by logdir
path = File.join(logdir, basename)
logfile = File.open(path, MODE, 0644)
# This will call Delegator's initialize method, so below this point
# we can call any method from File on our Logfile instances.
super(logfile)
end
end
अग्रेषित करने योग्य
दिलचस्प बात यह है कि रूबी की मानक लाइब्रेरी हमें 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
. के माध्यम से स्वचालित रूप से प्रदान नहीं किया जाता है , लेकिन इसके बजाय, प्रत्येक विधि के लिए स्पष्ट रूप से घोषित करने की आवश्यकता है जिसे हम अग्रेषित करना चाहते हैं। यह हमें लपेटी हुई वस्तु के किसी भी तरीके को "छिपाने" की अनुमति देता है जिसे हम अपने ग्राहकों के सामने प्रकट नहीं करना चाहते हैं, जो हमें हमारे सार्वजनिक इंटरफ़ेस पर अधिक नियंत्रण देता है और मुख्य कारण है कि मैं आमतौर पर Forwardable
पसंद करता हूं SimpleDelegator
. से अधिक ।
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
विधियाँ, अभी भी first_name
. को अग्रेषित करते समय और last_name
लपेटा हुआ User
वस्तु:
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"
यह फीचर कई बार काफी काम आ सकता है। मैंने अतीत में इसे समान इंटरफेस वाले पुस्तकालयों के बीच माइग्रेट कोड लेकिन विधि नामों के संबंध में अलग-अलग अपेक्षाओं जैसी चीजों के लिए सफलतापूर्वक उपयोग किया है।
मानक पुस्तकालय के बाहर
मानक पुस्तकालय में मौजूदा प्रतिनिधिमंडल समाधानों के बावजूद, रूबी समुदाय ने पिछले कुछ वर्षों में कई विकल्प विकसित किए हैं और हम उनमें से दो का पता लगाएंगे।
प्रतिनिधि
रेल की लोकप्रियता को ध्यान में रखते हुए, इसके Delegator
विधि अच्छी तरह से रूबी डेवलपर्स द्वारा उपयोग किए जाने वाले प्रतिनिधिमंडल का सबसे अधिक इस्तेमाल किया जाने वाला रूप हो सकता है। यहां बताया गया है कि हम अपने भरोसेमंद पुराने UserDecorator
. को फिर से लिखने के लिए इसका उपयोग कैसे कर सकते हैं :
# In a real Rails app this would most likely be a subclass of 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
. का उपयोग करने की आवश्यकता नहीं है चूंकि Delegator
सीधे Module
. पर परिभाषित किया गया है और इसलिए हर वर्ग या मॉड्यूल निकाय में उपलब्ध है (बेहतर या बदतर के लिए, आप तय करते हैं)। हालांकि, Delegator
इसकी आस्तीन में कुछ साफ-सुथरी चालें हैं। सबसे पहले, :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
।
Delegator
. का एक और दिलचस्प विकल्प क्या इसका :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
return वापस आ जाएगी इसके बजाय:
class UserDecorator
delegate :first_name, :last_name, to: :user, allow_nil: true
...
end
decorated_user = UserDecorator.new(nil)
decorated_user.first_name
#=> nil
कास्टिंग
अंतिम प्रतिनिधिमंडल विकल्प जिसे हम देख रहे हैं वह है जिम गे की Casting
मणि, जो डेवलपर्स को "रूबी में विधियों को सौंपने और स्वयं को संरक्षित करने" की अनुमति देता है। यह संभवतः प्रतिनिधिमंडल की सख्त परिभाषा के सबसे करीब है, क्योंकि यह रूबी की गतिशील प्रकृति का उपयोग अस्थायी रूप से एक विधि कॉल के रिसीवर को रिबाइंड करने के लिए करता है, जैसे:
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
. के साथ , जो हमें Delegator
. तक पहुंच प्रदान करता है तरीका। वैकल्पिक रूप से, हम इस्तेमाल कर सकते थे include Casting::Client
User
. के अंदर कक्षा सभी उदाहरणों को यह क्षमता देने के लिए।
इसके अतिरिक्त, 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
इस्तेमाल किया जा सकता है, जबकि रेल कोड अपने Delegator
. की ओर बढ़ता है तरीका। जो प्रत्यायोजित किया जाता है उस पर अधिकतम नियंत्रण के लिए, Casting
मणि एक उत्कृष्ट विकल्प है, हालांकि यह अन्य समाधानों की तुलना में थोड़ा अधिक जटिल है।
अतिथि लेखक माइकल कोहल का रूबी के साथ प्रेम संबंध 2003 के आसपास शुरू हुआ। उन्हें भाषा के बारे में लिखना और बोलना भी पसंद है और बैंकाक.आरबी और रूबीकॉन्फ थाईलैंड का सह-संयोजन करता है।