একটি পূর্ববর্তী রুবি ম্যাজিকে, আমরা শিখেছিলাম কিভাবে একটি ক্লাসের .new মেথড ওভাররাইট করে মডিউলগুলো নির্ভরযোগ্যভাবে ইনজেক্ট করা যায়, যা মেথডগুলোর সাথে অতিরিক্ত আচরণ যোগ করতে দেয়।
এবারের কথা, আমরা এই আচরণটিকে একটি স্বতন্ত্র মডিউলে বের করে আনার চেষ্টা করব যাতে এটি পুনরায় ব্যবহার করা যায়। আমরা একটি Wrappable মডিউল তৈরি করব যা ক্লাস এক্সটেনশন পরিচালনা করবে, এবং ক্লাস-লেভেল ইনস্ট্যান্স ভেরিয়েবল সম্পর্কে সব কিছু শিখব। চলুন শুরু করি!
Wrappable মডিউল পরিচিতি
যখন অবজেক্টগুলো ইনিশিয়ালাইজ হয় তখন সেগুলো মডিউলের সাথে র্যাপ করতে হলে, ক্লাসটিকে জানাতে হবে কোন র্যাপিং মডিউলগুলো ব্যবহার করতে হবে। চলুন একটি সাধারণ Wrappable মডিউল তৈরি করি যা একটি wrap মেথড প্রদান করবে যা দেওয়া মডিউলকে একটি ক্লাস অ্যাট্রিবিউট হিসেবে সংজ্ঞায়িত অ্যারেতে পুশ করবে। এছাড়াও, পূর্বের পোস্টে আলোচিত new মেথডটি পুনর্বিবেচনা করব।
module Wrappable
@@wrappers = []
def wrap(mod)
@@wrappers << mod
end
def new(*arguments, &block)
instance = allocate
@@wrappers.each { |mod| instance.singleton_class.include(mod) }
instance.send(:initialize, *arguments, &block)
instance
end
end
এই নতুন আচরণটি একটি ক্লাসে যোগ করতে আমরা extend ব্যবহার করি। extend মেথড দেওয়া মডিউলটিকে ক্লাসে যোগ করে। মেথডগুলো তখন ক্লাস মেথড হয়ে যায়। এই ক্লাসের ইনস্ট্যান্সগুলো র্যাপ করার জন্য একটি মডিউল যোগ করতে আমরা এখন wrap মেথড কল করতে পারি।
module Logging
def make_noise
puts "Started making noise"
super
puts "Finished making noise"
end
end
class Bird
extend Wrappable
wrap Logging
def make_noise
puts "Chirp, chirp!"
end
end
চলুন Bird-এর একটি নতুন ইনস্ট্যান্স তৈরি করে make_noise মেথড কল করে দেখি।
bird = Bird.new
bird.make_noise
# Started making noise
# Chirp, chirp!
# Finished making noise
দারুণ! এটি প্রত্যাশা অনুযায়ী কাজ করল। তবে, যখন আমরা দ্বিতীয় ক্লাসকে Wrappable মডিউলের সাথে এক্সটেন্ড করি, তখন কিছু অদ্ভুত ঘটে।
module Powered
def make_noise
puts "Powering up"
super
puts "Shutting down"
end
end
class Machine
extend Wrappable
wrap Powered
def make_noise
puts "Buzzzzzz"
end
end
machine = Machine.new
machine.make_noise
# Powering up
# Started making noise
# Buzzzzzz
# Finished making noise
# Shutting down
bird = Bird.new
bird.make_noise
# Powering up
# Started making noise
# Chirp, chirp!
# Finished making noise
# Shutting down
Machine Logging মডিউলের সাথে র্যাপ করা ন होने на ও, এটা লগিং তথ্য আউটপুট করছে। আরেকটা ভয়ানক ব্যাপার - পাখিটাও এখন পাওয়ার আপ এবং ডাউন করছে। এটি ঠিক নয়, কি?
এই সমস্যার মূল কারণ হল আমরা মডিউলগুলো কিভাবে সংরক্ষণ করছি। ক্লাস ভেরিয়েবল @@wrappers Wrappable মডিউলে সংজ্ঞায়িত এবং যেকোনো ক্লাসে wrap ব্যবহার করলে সেটি ব্যবহার হয়।
এটি আরও স্পষ্ট হয় যখন আমরা Wrappable মডিউল এবং Bird ও Machine ক্লাসের ক্লাস ভেরিয়েবলগুলো দেখি। Wrappable-এর একটি ক্লাস ভেরিয়েবল আছে, কিন্তু দুটি ক্লাসের কোনোটিই নেই।
Wrappable.class_variables # => [:@@wrappers]
Bird.class_variables # => []
Machine.class_variables # => []
এটি ঠিক করতে হলে, আমাদের ইমপ্লিমেন্টেশন পরিবর্তন করতে হবে যাতে এটি ইনস্ট্যান্স ভেরিয়েবল ব্যবহার করে। তবে, এগুলো Bird বা Machine-এর ইনস্ট্যান্সের ভেরিয়েবল নয়, বরং ক্লাসগুলো নিজেদের ইনস্ট্যান্স ভেরিয়েবল।
রুবিতে, ক্লাসগুলোই শুধু অবজেক্ট
এটি প্রথমে একটু মাথা ঘুরানোর মতো, কিন্তু খুব গুরুত্বপূর্ণ একটি ধারণা। ক্লাসগুলো Class-এর ইনস্ট্যান্স এবং class Bird; end লেখা Bird = Class.new লেখার সমতুল্য। আরও বিভ্রান্ত করতে, Class Module-এর থেকে ইনহেরিট করেছে যা আবার Object-এর থেকে ইনহেরিট করেছে। ফলস্বরূপ, ক্লাস এবং মডিউলদের অন্য যেকোনো অবজেক্টের মতো একই মেথড থাকে। ক্লাসে ব্যবহার করা অধিকাংশ মেথড (যেমন attr_accessor ম্যাক্রো) আসলে Module-এর ইনস্ট্যান্স মেথড।
ক্লাসে ইনস্ট্যান্স ভেরিয়েবল ব্যবহার
চলুন Wrappable ইমপ্লিমেন্টেশনকে ইনস্ট্যান্স ভেরিয়েবল ব্যবহার করতে পরিবর্তন করি। জিনিসগুলো পরিষ্কার রাখতে, আমরা একটি wrappers মেথড চালু করব যা অ্যারে সেট আপ করবে বা ইনস্ট্যান্স ভেরিয়েবল ইতিমধ্যে থাকলে সেটি রিটার্ন করবে। আমরা wrap এবং new মেথডগুলোকেও সংশোধন করব যাতে সেগুলো এই নতুন মেথড ব্যবহার করে।
module Wrappable
def wrap(mod)
wrappers << mod
end
def wrappers
@wrappers ||= []
end
def new(*arguments, &block)
instance = allocate
wrappers.each { |mod| instance.singleton_class.include(mod) }
instance.send(:initialize, *arguments, &block)
instance
end
end
যখন আমরা মডিউল এবং দুটি ক্লাসের ইনস্ট্যান্স ভেরিয়েবল চেক করি, তখন দেখতে পাই Bird এবং Machine উভয়ই নিজেদের র্যাপিং মডিউল সংগ্রহ বজায় রাখছে।
Wrappable.instance_variables #=> []
Bird.instance_variables #=> [:@wrappers]
Machine.instance_variables #=> [:@wrappers]
আশ्चর্য নয়, এটি পূর্বে দেখা সমস্যাটি সমাধান করে - এখন উভয় ক্লাস তাদের নিজস্ব মডিউলերով র্যাপ হয়ে আছে।
bird = Bird.new
bird.make_noise
# Started making noise
# Chirp, chirp!
# Finished making noise
machine = Machine.new
machine.make_noise
# Powering up
# Buzzzzzz
# Shutting down
ইনহেরিটেন্স সমর্থন
সবকিছু খুব ভালো চলছিল, যতক্ষণ না ইনহেরিটেন্স আসে। আমরা আশা করি ক্লাসগুলো সুপারক্লাস থেকে র্যাপিং মডিউল ইনহেরিট করবে। চলুন চেক করি।
module Flying
def make_noise
super
puts "Is flying away"
end
end
class Pigeon < Bird
wrap Flying
def make_noise
puts "Coo!"
end
end
pigeon = Pigeon.new
pigeon.make_noise
# Coo!
# Is flying away
দেখতে পাচ্ছেন, এটি প্রত্যাশা অনুযায়ী কাজ করল না, কারণ Pigeonও নিজস্ব র্যাপিং মডিউল সংগ্রহ বজায় রাখছে। Pigeon-এর জন্য সংজ্ঞায়িত র্যাপিং মডিউলগুলো Bird-এ না থাকা বোঝাতে পারো, কিন্তু আমরা যা চাই সেটা নয়। চলুন পূর্ণ ইনহেরিটেন্স চেইন থেকে সব র্যাপার পাওয়ার উপায় খুঁজে বের করি।
শুভ তা, রুবি Module#ancestors মেথড প্রদান করে যা একটি ক্লাস (অথবা মডিউল) যে সব ক্লাস এবং মডিউল থেকে ইনহেরিট করেছে তাদের তালিকা দেয়।
Pigeon.ancestors # => [Pigeon, Bird, Object, Kernel, BasicObject]
একটি grep কল যোগ করে, আমরা যেগুলো আসলে Wrappable দিয়ে এক্সটেন্ড করা সেগুলো বের করতে পারি। যেহেতু আমরা চেইন”上রে থাকা র্যাপারগুলো দিয়ে প্রথমে র্যাপ করতে চাই, আমরা অর্ডার উল্টাতে .reverse কল করি।
Pigeon.ancestors.grep(Wrappable).reverse # => [Bird, Pigeon]
রুবির #=== মেথড
রুবির কিছু জাদু #=== (বা কেস ইকুয়ালিটি) মেথড থেকে আসে। ডিফল্টভাবে, এটি #== (বা ইকুয়ালিটি) মেথডের মতো আচরণ করে। কিন্তু, বেশ কিছু ক্লাস #=== মেথড ওভাররাইড করে case স্টেটমেন্টে ভিন্ন আচরণ প্রদান করে। এটাই কারণে আপনি রেগুলার এক্সপ্রেশন (#=== সমতুল্য #match?), বা ক্লাস (#=== সমতুল্য #kind_of?) কেস স্টেটমেন্টে ব্যবহার করতে পারেন। Enumerable#grep, Enumerable#all?, বা Enumerable#any? এর মতো মেথডগুলোকেও কেস ইকুয়ালিটি মেথডের উপর নির্ভর করতে হয়।
এখন আমরা flat_map(&:wrappers) কল করে ইনহেরিটেন্স চেইনে সংজ্ঞায়িত সব র্যাপার একটি একক অ্যারে হিসেবে পেতে পারি।
Pigeon.ancestors.grep(Wrappable).reverse.flat_map(&:wrappers) # => [Logging]
বাকি হল এটিকে একটি inherited_wrappers মডিউলে প্যাক করা এবং new মেথড কিছুটা সংশোধন করা যাতে এটি wrappers মেথডের বদলে সেটি ব্যবহার করে।
module Wrappable
def inherited_wrappers
ancestors
.grep(Wrappable)
.reverse
.flat_map(&:wrappers)
end
def new(*arguments, &block)
instance = allocate
inherited_wrappers.each { |mod| instance.singleton_class.include(mod) }
instance.send(:initialize, *arguments, &block)
instance
end
end
একটি চূড়ান্ত টেস্ট রান নিশ্চিত করে যে এখন সবকিছু প্রত্যাশা অনুযায়ী কাজ করছে। র্যাপিং মডিউলগুলো শুধু সেই ক্লাসে (এবং এর সাবক্লাসগুলিতে) প্রযোজ্য হয় যেখানে সেগুলো প্রয়োগ করা হয়েছে।
bird = Bird.new
bird.make_noise
# Started making noise
# Chirp, chirp!
# Finished making noise
machine = Machine.new
machine.make_noise
# Powering up
# Buzzzzz
# Shutting down
pigeon = Pigeon.new
pigeon.make_noise
# Started making noise
# Coo!
# Finished making noise
# Is flying away
এখনো শেষ নয়!
মানা পড়ে, শোরগুলো পাখিরা একটু তাত্ত্বিক উদাহরণ (টুইট, টুইট)। কিন্তু ইনহেরিটেবল ক্লাস ইনস্ট্যান্স ভেরিয়েবল শুধু ক্লাস কীভাবে কাজ করে সেটা বোঝার জন্যই কুল নয়। এগুলো একটি চমৎকার উদাহরণ যে রুবিতে ক্লাসগুলো শুধু অবজেক্ট।
এবং আমরা মানব, ইনহেরিটেবল ক্লাস ইনস্ট্যান্স ভেরিয়েবল বাস্তব জীবনেও খুবই উপকারী হতে পারে। উদাহরণস্বরূপ, একটি মডেলে অ্যাট্রিবিউট এবং রিলেশনশিপ সংজ্ঞায়িত করার সময় পরে সেগুলো ইনট্রোস্পেক্ট করার ক্ষমতা থাকলে। আমাদের জন্য জাদু হলো এর সাথে খেলা এবং জিনিসগুলো কীভাবে কাজ করে সে সম্পর্কে আরও ভালো বোঝা। এবং আপনার মন নতুন স্তরের সমাধানের জন্য খুলে দাও। 🧙🏼♀️
সবসময়ের মতো, আমরা আগ্রহী hogy আপনি এই বা অনুরূপ প্যাটার্ন ব্যবহার করে কী বানান। শুধু @AppSignal-এ টুইট করুন।