এই পোস্টে, আমরা এক্সপ্লোর করব কীভাবে ব্যবহারকারীদের দ্বারা কনফিগারযোগ্য রুবি মডিউল তৈরি করা যায়—একটি প্যাটার্ন যা জেম অথরদের তাদের লাইব্রেরিতে আরও ফ্লেক্সিবিলিটি যোগ করতে সক্ষম করে। অধিকাংশ রুবি ডেভেলপার মডিউল ব্যবহার করে বিহেবিয়ার শেয়ার করার সাথে পরিচিত। সেটি তাদের প্রধান ইউজ কেসের একটা, ডকুমেন্টেশন অনুযায়ী:
রেলসরুবিতে মডিউল দুটি উদ্দেশ্য szolgál করে, নেমস্পেসিং এবং মিক্সিন ফাংশনালিটি।
ActiveSupport::Concern ফরমে কিছু সিনট্যাকটিক চিনি যোগ করেছে, কিন্তু সাধারণ নীতি একই থাকে।
সমস্যাটি
মডিউল ব্যবহার করে মিক্সিন ফাংশনালিটি প্রদান করা সাধারণত সহজ। আমাদের শুধু কিছু মেথড বান্ডেল করতে হয় এবং আমাদের মডিউল কোথাও ইনক্লুড করতে হয়:
module HelloWorld def hello "Hello, world!" end end class Test include HelloWorld end Test.new.hello #=> "Hello, world!" এটি একটি প্রিটি স্ট্যাটিক মেকানিজম, যদিও রুবির inherited এবং extended হুক মেথড ইনক্লুডিং ক্লাসের ভিত্তিতে কিছু ভিন্ন বিহেবিয়ার আਫার করে: module HelloWorld def self.included(base) define_method :hello do "Hello, world from #{base}!" end end end class Test include HelloWorld end Test.new.hello #=> "Hello, world from Test!" এটি কিছুটা বেশি ডাইনামিক কিন্তু এখনও আমাদের কোড ইউজারদের মডিউল ইনক্লুশন টাইমে hello মেথড রিনেম করার অনুমতি দেয় না।
সমাধান: কনফিগারেবল রুবি মডিউল
গত কয়েক বছরে, একটি নতুন প্যাটার্ন উত্থান করেছে যা এই সমস্যাটি সমাধান করে, যাকে কখনো কখনো "মডিউল বিল্ডার প্যাটার্ন" বলা হয়। এই টেকনিক দুটি প্রাইমারি রুবি ফিচারের ওপর নির্ভর করে:
মডিউল অন্য যেকোনো অবজেক্টের মত—সেগুলো অন দ ফ্লাই তৈরি করা যায়, ভেরিয়েবলে অ্যাসাইন করা যায়, ডাইনামিক্যালি মডিফাই করা যায়, মেথডে পাস করা বা রিটার্ন করা যায়।
def make_module # অন দ ফ্লাই একটি মডিউল তৈরি করুন এবং ভেরিয়েবলে অ্যাসাইন করুন mod = Module.new # মডিউল মডিফাই করুন mod.module_eval do def hello "Hello, AppSignal world!" end end # এক্সপ্লিসিটলি রিটার্ন করুন mod endincludeবাextendকলের আর্গুমেন্ট হয়ে একটি মডিউল থাকতে হয় না, সেটি একটি এক্সপ্রেশনও হতে পারে যেটি একটি মডিউল রিটার্ন করে, যেমন একটি মেথড কল।class Test # make_module দ্বারা রিটার্ন করা মডিউল ইনক্লুড করুন include make_module end Test.new.hello #=> "Hello, AppSignal world!"
মডিউল বিল্ডার একশনে
আমরা এখন এই জ্ঞান ব্যবহার করে একটি সিম্পল মডিউল Wrapper বিল্ড করব, যা নিম্নলিখিত বিহেবিয়ার ইমপ্লিমেন্ট করে:
Wrapperইনক্লুড করার ক্লাস শুধু একটি নির্দিষ্ট টাইপের অবজেক্ট র্যাপ করতে পারবে। কনস্ট্রাক্টর আর্গুমেন্ট টাইপ যাচাই করবে এবং টাইপ মিসম্যাচ হলে এরর রেইজ করবে।- র্যাপ করা অবজেক্ট একটি ইনস্ট্যান্স মেথড দিয়ে অ্যাক্সেসযোগ্য থাকবে যেটির নাম
original_, যেমনoriginal_integerবাoriginal_string। - এটি আমাদের কোডের কনসামারদের এই অ্যাকসেসর মেথডের জন্য একটি অল্টারনেটিভ নাম নির্দিষ্ট করতে দেবে, উদাহরণস্বরূপ
the_string।
# 1 class IntWrapper # 2 include Wrapper.for(Integer) end # 3 i = IntWrapper.new(42) i.original_integer #=> 42 # 4 i = IntWrapper.new("42") #=> TypeError (not a Integer) # 5 class StringWrapper include Wrapper.for(String, accessor_name: :the_string) end s = StringWrapper.new("Hello, World!") # 6 s.the_string #=> "Hello, World!" ধাপ ১-এ, আমরা IntWrapper নামে একটি নতুন ক্লাস ডিফাইন করি। ধাপ ২-এ, আমরা নিশ্চিত করি যে এই ক্লাস শুধু নাম দিয়ে একটি মডিউল ইনক্লুড করে না বরং Wrapper.for(Integer) কলের রেজাল্ট মিক্স ইন করে। ধাপ ৩-এ, আমরা আমাদের নতুন ক্লাসের একটি অবজেক্ট ইনস্ট্যানশিয়েট করি এবং i-এ অ্যাসাইন করি। নির্দিষ্টanusar, এই অবজেক্টের একটি মেথড আছে original_integer নামে, যা আমাদের রিকোয়ার্মেন্টের একটা স্যাটিসফাই করে। ধাপ ৪-এ, যদি আমরা ভুল টাইপের আর্গুমেন্ট পাস করি, যেন একটি স্ট্রিং, তবে একটি হেল্পফুল TypeError রেইজ হবে। চূড়ান্তে, যাচাই করুন যে ইউজাররা কাস্টম অ্যাকসেসর নাম স্পেসিফাই করতে পারেন। এর জন্য, ধাপ ৫-এ StringWrapper নামে একটি নতুন ক্লাস ডিফাইন করি এবং কীওয়ার্ড আর্গুমেন্ট accessor_name হিসেবে the_string পাস করি, যা ধাপ ৬-এ একশনে দেখতে পাচ্ছি। মনে রাখবেন এটি কিছুটা কন্ট্রাইভড উদাহরণ, কিন্তু মডিউল বিল্ডার প্যাটার্ন এবং এর ব্যবহার দেখানোর জন্য যথেষ্ট ভিন্ন বিহেবিয়ার আছে।
প্রথম প্রচেষ্টা
রিকোয়ার্মেন্ট এবং ব্যবহার উদাহরণের ভিত্তিতে, আমরা এখন ইমপ্লিমেন্টেশন শুরু করতে পারি। আমরা ইতিমধ্যে জানি যে আমাদের Wrapper নামে একটি মডিউল প্রয়োজন যার মডিউল-লেভেল মেথড থাকবে for নামে, যা একটি ক্লাস নেবে এবং একটি অপশনাল কীওয়ার্ড আর্গুমেন্ট:
module Wrapper def self.for(klass, accessor_name: nil) end end যেহেতু এই মেথডের রিটার্ন ভ্যালু include-এর আর্গুমেন্ট হয়ে যাবে, তাই এটি একটি মডিউল হতে হবে। সুতরাং, আমরা Module.new দিয়ে একটি নতুন আনোনিমাস মডিউল তৈরি করতে পারি। Module.new do end আমাদের রিকোয়ার্মেন্ট অনুযায়ী, এই মডিউলটিকে একটি কনস্ট্রাক্টর ডিফাইন করতে হবে যা পাস করা অবজেক্টের টাইপ যাচাই করে, সেইসাথে একটি উপযুক্ত নামে অ্যাকসেসর মেথড। কনস্ট্রাক্টর দিয়ে শুরু করি: define_method :initialize do |object| raise TypeError, "not a #{klass}" unless object.is_a?(klass) @object = object end এই কোড টুকরো define_method ব্যবহার করে ডাইনামিক্যালি একটি ইনস্ট্যান্স মেথড যোগ করে রিসিভারে। যেহেতু ব্লকটি একটি ক্লোজার হিসেবে কাজ করে, এটি আউটার স্কোপ থেকে klass অবজেক্ট ব্যবহার করতে পারে প্রয়োজনীয় টাইপ চেক করার জন্য। উপযুক্ত নামে একটি অ্যাকসেসর মেথড যোগ করা ততটাও কঠিন নয়: # 1 method_name = accessor_name || begin klass_name = klass.to_s.gsub(/(.)([A-Z])/,'\1_\2').downcase "original_#{klass_name}" end # 2 define_method(method_name) { @object } প্রথমে, আমরা চেক করি আমাদের কোডের কলার কি accessor_name পাস করেছে। যদি হয়, তবে সেটি method_name-এ অ্যাসাইন করি এবং কাজ শেষ। অন্যথায়, আমরা ক্লাসটি নেই এবং সেটিকে একটি আন্ডারস্কোরড স্ট্রিংএ কনভার্ট করি, উদাহরণস্বরূপ Integer হয়ে যায় integer বা OpenStruct হয়ে যায় open_struct। এই klass_name ভেরিয়েবলকে original_ দিয়ে প্রিফিক্স করে চূড়ান্ত অ্যাকসেসর নাম জেনারেট করা হয়। মেথডের নাম জানার পর, আমরা আবার define_method ব্যবহার করে এটিকে আমাদের মডিউলে যোগ করি, ধাপ ২-এ দেখানো হয়েছে। এখানে এই মুহূর্ত পর্যন্ত সম্পূর্ণ কোড। একটি ফ্লেক্সিবল এবং কনফিগারেবল রুবি মডিউল জন্য ২০ লাইনের কম—খারাপ নয়। module Wrapper def self.for(klass, accessor_name: nil) Module.new do define_method :initialize do |object| raise TypeError, "not a #{klass}" unless object.is_a?(klass) @object = object end method_name = accessor_name || begin klass_name = klass.to_s.gsub(/(.)([A-Z])/,'\1_\2').downcase "original_#{klass_name}" end define_method(method_name) { @object } end end end চোখদার পাঠক মনে রাখতে পারেন যে Wrapper.for একটি আনোনিমাস মডিউল রিটার্ন করে। এতে কোনো সমস্যা নেই, কিন্তু একটা কনফিউশন হতে পারে একটি অবজেক্টের ইনহেরিট্যান্স চেইন পরীক্ষা করার সময়: StringWrapper.ancestors #=> [StringWrapper, #, Object, Kernel, BasicObject] এখানে # (নাম ভিন্ন হবে আপনি ফলো করলে) আমাদের আনোনিমাস মডিউলকে রেফার করে।
উন্নত সংস্করণ
আমাদের ইউজারদের জীবন সহজ করতে, একটি নামযুক্ত মডিউল রিটার্ন করি আনোনিমাসটির বদলে। এই কোড আগেরটা খুব মিলitus, কিছু মাইনর চেঞ্জসহ:
module Wrapper def self.for(klass, accessor_name: nil) # 1 mod = const_set("#{klass}InstanceMethods", Module.new) # 2 mod.module_eval do define_method :initialize do |object| raise TypeError, "not a #{klass}" unless object.is_a?(klass) @object = object end method_name = accessor_name || begin klass_name = klass.to_s.gsub(/(.)([A-Z])/, '\1_\2').downcase "original_#{klass_name}" end define_method(method_name) { @object } end # 3 mod end end প্রথম ধাপে, আমরা একটি নেস্টেড মডিউল তৈরি করি "#{klass}InstanceMethods" নামে (উদাহরণস্বরূপ IntegerInstanceMethods), যা শুধু একটি "খালি" মডিউল। ধাপ ২-এ দেখানো হয়েছে, আমরা for মেথডে module_eval ব্যবহার করি, যা ব্লকটি মডিউলের কনটেক্সটে ইভ্যালুয়েট করে। এইভাবে, রিটার্ন করার আগে মডিউলে বিহেবিয়ার যোগ করতে পারি। ধাপ ৩-এ, মডিউল রিটার্ন করি। এখন যদি আমরা Wrapper ইনক্লুড করার ক্লাসের ancestors পরীক্ষা করি, আউটপুটে একটি সঠিক নামযুক্ত মডিউল থাকবে, যা পূর্বের আনোনিমাস মডিউলের চেয়ে অনেক অর্থবহ এবং ডিবাগ করা সহজ। StringWrapper.ancestors #=> [StringWrapper, Wrapper::StringInstanceMethods, Object, Kernel, BasicObject]
প্রকৃতিতে মডিউল বিল্ডার প্যাটার্ন
এই পোস্ট ছাড়াও, মডিউল বিল্ডার প্যাটার্ন বা সাদৃশ্য টেকনিক কোথাও পাওয়া যায়? একটি উদাহরণ হল dry-rb জেম ফ্যামিলি, যেখানে, উদাহরণস্বরূপ, dry-effects মডিউল বিল্ডার ব্যবহার করে কনফিগারেশন অপশন পাস করে বিভিন্ন ইফেক্ট হ্যান্ডলারের কাছে:
# এটি একটি `counter` ইফেক্ট প্রোভাইডার যোগ করে। এটি ইফেক্ট হ্যান্ডেল করবে (এলিমিনেট) include Dry::Effects::Handler.State(:counter) # স্কোপ প্রদান করা আবশ্যক # সব ক্যাশ ভ্যালু এই কী দিয়ে স্কোপড হবে include Dry::Effects::Handler.Cache(:blog) আমরা সাদৃশ্য ব্যবহার পাই অতুলনীয় Shrine জেমে, যা রুবি অ্যাপ্লিকেশনের জন্য একটি ফাইল আপলোড টুলকিট প্রদান করে: class Photo < Sequel::Model include Shrine::Attachment(:image) end এই প্যাটার্ন এখনো তুলনামূলক নতুন, কিন্তু আমি আশা করি ভবিষ্যতে এর আরও ব্যবহার দেখব, বিশেষ করে সেই জেমগুলোর মধ্যে যেগুলো রেলস অ্যাপ্লিকেশনের চেয়ে পিউর রুবি অ্যাপ্লিকেশনে বেশি ফোকাস করে।
সারাংশ
এই পোস্টে, আমরা এক্সপ্লোর করেছি কীভাবে রুবিতে কনফিগারেবল মডিউল ইমপ্লিমেন্ট করা যায়, একটি টেকনিক যাকে কখনো কখনো মডিউল বিল্ডার প্যাটার্ন বলা হয়। অন্যান্য মেটা প্রোগ্রামিং টেকনিকের মত, এটিও কমপ্লেক্সিটির খরচে আসে এবং তাই Без কারন ব্যবহার করা উচিত নয়। য Âu কেসে এমন ফ্লেক্সিবিলিটি প্রয়োজন, সেখানে রুবির অবজেক্ট মডেল একবার আবার একটি ইলিগ্যান্ট এবং সংক্ষিপ্ত সমাধান দিয়েছে। মডিউল বিল্ডার প্যাটার্ন এমন কিছু যা অধিকাংশ রুবি ডেভেলপারকে খুব কম প্রয়োজন পড়ে, তবে এটি একটা চমৎকার টুল হাতে রাখার মতো, বিশেষ করে লাইব্রেরি অথরদের জন্য। P.S. আপনি যদি Ruby Magic পোস্টগুলো প্রেস থেকে বের होते পড়তে চান, আমাদের Ruby Magic নিউজলেটার সাবস্ক্রাইব করুন এবং কখনো একটি পোস্ট মিস করবেন না!