এই পোস্টে, আমরা কীভাবে রুবি মডিউলগুলি তৈরি করব যা আমাদের কোডের ব্যবহারকারীদের দ্বারা কনফিগার করা যায় তা অন্বেষণ করব — একটি প্যাটার্ন যা রত্ন লেখকদের তাদের লাইব্রেরিতে আরও নমনীয়তা যোগ করতে দেয়৷
বেশিরভাগ রুবি বিকাশকারীরা আচরণ ভাগ করার জন্য মডিউল ব্যবহার করার সাথে পরিচিত। সর্বোপরি, ডকুমেন্টেশন অনুসারে এটি তাদের প্রধান ব্যবহারের ক্ষেত্রে একটি:
মডিউলগুলি রুবিতে দুটি উদ্দেশ্য পরিবেশন করে, নেমস্পেসিং এবং মিক্সিন কার্যকারিতা।
রেলগুলি 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 # create a module on the fly and assign it to variable mod = Module.new # modify module mod.module_eval do def hello "Hello, AppSignal world!" end end # explicitly return it mod end
-
include
করার যুক্তি অথবাextend
কলগুলিকে একটি মডিউল হতে হবে না, এটি একটি অভিব্যক্তিও হতে পারে যা ফেরত দেয়, যেমন একটি পদ্ধতি কল।class Test # include the module returned by make_module include make_module end Test.new.hello #=> "Hello, AppSignal world!"
মডিউল বিল্ডার ইন অ্যাকশন
আমরা এখন এই জ্ঞান ব্যবহার করব Wrapper
নামে একটি সাধারণ মডিউল তৈরি করতে , যা নিম্নলিখিত আচরণ প্রয়োগ করে:
-
Wrapper
সহ একটি ক্লাস শুধুমাত্র একটি নির্দিষ্ট ধরনের বস্তু মোড়ানো যাবে. কনস্ট্রাক্টর আর্গুমেন্টের ধরন যাচাই করবে এবং টাইপটি প্রত্যাশিতটির সাথে মেলে না হলে একটি ত্রুটি উত্থাপন করবে৷ - মোড়ানো বস্তুটি
original_<class>
নামক একটি উদাহরণ পদ্ধতির মাধ্যমে উপলব্ধ হবে , যেমন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!"
ধাপ 1 এ, আমরা IntWrapper
নামে একটি নতুন ক্লাস সংজ্ঞায়িত করি .
ধাপ 2-এ, আমরা নিশ্চিত করি যে এই শ্রেণীতে কেবল নাম দ্বারা একটি মডিউল অন্তর্ভুক্ত করা হয় না বরং এর পরিবর্তে, Wrapper.for(Integer)
-এ একটি কলের ফলাফলে মিশে যায়। .
ধাপ 3 এ, আমরা আমাদের নতুন ক্লাসের একটি অবজেক্ট ইনস্ট্যান্ট করি এবং এটি i
-এ বরাদ্দ করি . যেমন উল্লেখ করা হয়েছে, এই বস্তুটির original_integer
নামে একটি পদ্ধতি রয়েছে , যা আমাদের একটি প্রয়োজনীয়তা পূরণ করে।
ধাপ 4-এ, যদি আমরা ভুল টাইপের একটি যুক্তিতে পাস করার চেষ্টা করি, যেমন একটি স্ট্রিং, একটি সহায়ক TypeError
উত্থাপিত হবে অবশেষে, আসুন যাচাই করি যে ব্যবহারকারীরা কাস্টম অ্যাক্সেসর নাম নির্দিষ্ট করতে সক্ষম৷
এর জন্য, আমরা StringWrapper
নামে একটি নতুন ক্লাস সংজ্ঞায়িত করি ধাপ 5 এ এবং the_string
পাস করুন কীওয়ার্ড আর্গুমেন্ট হিসাবে accessor_name
, যা আমরা ধাপ 6 এ কর্মে দেখতে পাই।
যদিও এটি স্বীকৃতভাবে একটি কিছুটা কল্পিত উদাহরণ, তবে মডিউল নির্মাতা প্যাটার্ন এবং এটি কীভাবে ব্যবহার করা হয় তা দেখানোর জন্য এটিতে যথেষ্ট পরিবর্তিত আচরণ রয়েছে৷
প্রথম প্রচেষ্টা
প্রয়োজনীয়তা এবং ব্যবহারের উদাহরণের উপর ভিত্তি করে, আমরা এখন বাস্তবায়ন শুরু করতে পারি। আমরা ইতিমধ্যেই জানি যে আমাদের 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
ব্যবহার করি এটি আমাদের মডিউলে যোগ করতে, যেমনটি ধাপ 2 এ দেখানো হয়েছে।
এখানে এই বিন্দু পর্যন্ত সম্পূর্ণ কোড আছে. একটি নমনীয় এবং কনফিগারযোগ্য রুবি মডিউলের জন্য 20 টিরও কম লাইন; খুব খারাপ না।
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, #<Module:0x0000000107283680>, Object, Kernel, BasicObject]
এখানে #<Module:0x0000000107283680>
(আপনি অনুসরণ করলে নামটি ভিন্ন হবে) আমাদের বেনামী মডিউলকে বোঝায়।
উন্নত সংস্করণ
আসুন একটি বেনামীর পরিবর্তে একটি নামযুক্ত মডিউল ফিরিয়ে দিয়ে আমাদের ব্যবহারকারীদের জীবনকে সহজ করে তুলি৷ এর জন্য কোডটি আমাদের আগে যা ছিল তার সাথে খুব মিল, কিছু ছোটখাটো পরিবর্তন সহ:
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
), এটি একটি "খালি" মডিউল।
ধাপ 2 এ দেখানো হয়েছে, আমরা module_eval
ব্যবহার করি for
-এ পদ্ধতি, যা মডিউলের প্রেক্ষাপটে কোডের একটি ব্লক মূল্যায়ন করে যা এটি কল করা হয়েছে। এইভাবে, আমরা মডিউলটি 3 ধাপে ফিরিয়ে দেওয়ার আগে আচরণ যোগ করতে পারি।
যদি আমরা এখন Wrapper
সহ একটি শ্রেণীর পূর্বপুরুষ পরীক্ষা করি , আউটপুট একটি সঠিকভাবে নামের মডিউল অন্তর্ভুক্ত করবে, যা আগের বেনামী মডিউলের তুলনায় অনেক বেশি অর্থবহ এবং ডিবাগ করা সহজ৷
StringWrapper.ancestors
#=> [StringWrapper, Wrapper::StringInstanceMethods, Object, Kernel, BasicObject]
মডিউল বিল্ডার প্যাটার্ন ইন দ্য ওয়াইল্ড
এই পোস্টটি ছাড়াও, আমরা মডিউল নির্মাতা প্যাটার্ন বা অনুরূপ কৌশলগুলি আর কোথায় পেতে পারি?
একটি উদাহরণ হল dry-rb
রত্ন পরিবার, যেখানে, উদাহরণস্বরূপ, dry-effects
বিভিন্ন ইফেক্ট হ্যান্ডলারে কনফিগারেশন অপশন পাস করতে মডিউল বিল্ডার ব্যবহার করে:
# This adds a `counter` effect provider. It will handle (eliminate) effects
include Dry::Effects::Handler.State(:counter)
# Providing scope is required
# All cache values will be scoped with this key
include Dry::Effects::Handler.Cache(:blog)
আমরা চমৎকার শ্রাইন মণিতে একই ধরনের ব্যবহার খুঁজে পেতে পারি, যা রুবি অ্যাপ্লিকেশনের জন্য একটি ফাইল আপলোড টুলকিট প্রদান করে:
class Photo < Sequel::Model
include Shrine::Attachment(:image)
end
এই প্যাটার্নটি এখনও তুলনামূলকভাবে নতুন, কিন্তু আমি আশা করি ভবিষ্যতে আমরা এটির আরও অনেক কিছু দেখতে পাব, বিশেষ করে রত্নগুলিতে যেগুলি রেলগুলির চেয়ে বিশুদ্ধ রুবি অ্যাপ্লিকেশনগুলিতে বেশি ফোকাস করে৷
সারাংশ
এই পোস্টে, আমরা রুবিতে কনফিগারযোগ্য মডিউলগুলি কীভাবে প্রয়োগ করতে হয় তা অনুসন্ধান করেছি, একটি কৌশল যা কখনও কখনও মডিউল নির্মাতা প্যাটার্ন হিসাবে উল্লেখ করা হয়। অন্যান্য মেটাপ্রোগ্রামিং কৌশলগুলির মতো, এটি বর্ধিত জটিলতার মূল্যে আসে এবং তাই সঠিক কারণ ছাড়া ব্যবহার করা উচিত নয়। যাইহোক, বিরল ক্ষেত্রে যেখানে এই ধরনের নমনীয়তা প্রয়োজন, রুবির অবজেক্ট মডেল আবার একটি মার্জিত এবং সংক্ষিপ্ত সমাধানের জন্য অনুমতি দেয়। মডিউল নির্মাতা প্যাটার্নটি এমন কিছু নয় যা বেশিরভাগ রুবি বিকাশকারীদের প্রায়শই প্রয়োজন হয়, তবে এটি একটি দুর্দান্ত সরঞ্জাম, বিশেষ করে লাইব্রেরি লেখকদের জন্য।
পি.এস. আপনি যদি রুবি ম্যাজিক পোস্টগুলি প্রেস থেকে বের হওয়ার সাথে সাথে পড়তে চান তবে আমাদের রুবি ম্যাজিক নিউজলেটারে সাবস্ক্রাইব করুন এবং একটি পোস্ট মিস করবেন না!