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