রুবির স্ট্যান্ডার্ড লাইব্রেরিতে লুকানো রত্নগুলির আজকের অন্বেষণে, আমরা প্রতিনিধিদের দেখতে যাচ্ছি৷
দুর্ভাগ্যবশত, এই শব্দটি - অন্যান্য অনেকের মতো - বছরের পর বছর ধরে কিছুটা ঘোলাটে হয়ে গেছে এবং বিভিন্ন লোকের কাছে ভিন্ন জিনিস বোঝায়। উইকিপিডিয়া অনুসারে:
ডেলিগেশন বলতে বোঝায় একটি বস্তুর (প্রাপক) সদস্যের (সম্পত্তি বা পদ্ধতি) মূল্যায়ন অন্য মূল বস্তুর (প্রেরক) প্রসঙ্গে। ডেলিগেশন স্পষ্টভাবে করা যেতে পারে, পাঠানোর বস্তুকে রিসিভিং অবজেক্টে পাস করার মাধ্যমে, যেটি যেকোনো বস্তু-ভিত্তিক ভাষায় করা যেতে পারে; অথবা অন্তর্নিহিতভাবে, ভাষাটির সদস্য অনুসন্ধানের নিয়ম দ্বারা, যার বৈশিষ্ট্যটির জন্য ভাষা সমর্থন প্রয়োজন৷
যাইহোক, প্রায়শই, লোকেরা একটি বস্তুকে ব্যাখ্যা করার জন্যও শব্দটি ব্যবহার করে যেটিকে অন্য বস্তুর সংশ্লিষ্ট পদ্ধতিকে একটি যুক্তি হিসাবে পাস না করেই বলা হয়, যা আরও স্পষ্টভাবে "ফরওয়ার্ডিং" হিসাবে উল্লেখ করা যেতে পারে।
এর বাইরে, আমরা নিবন্ধের বাকি অংশের জন্য এই উভয় নিদর্শন বর্ণনা করতে "প্রতিনিধি" ব্যবহার করব৷
প্রতিনিধি
আসুন রুবিতে আমাদের প্রতিনিধিদের অন্বেষণ শুরু করি স্ট্যান্ডার্ড লাইব্রেরির 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"
ডেলিগেট মেথড
শেষ প্রতিনিধি প্যাটার্ন 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')
# 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
ফেরত দেবে পরিবর্তে:
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 সালের দিকে শুরু হয়েছিল। তিনি ভাষা সম্পর্কে লিখতে এবং বলতেও উপভোগ করেন এবং Bangkok.rb এবং RubyConf থাইল্যান্ডের সহ-সংগঠিত হন।