Ruby একটি অবজেক্ট-ওরিয়েন্টেড ল্যাঙ্গুয়েজ হওয়ার কারণে, আমরা ওয়ার্লডকে অবজেক্টের একটি সেট হিসেবে মডেল করতে সুচินুই। আমরা বলি দুটি ইন্টিজার (x এবং y) হচ্ছে একটি Point, এবং একটি Line-এর দুটি Point থাকে।
এই অ্যাপ্রোচ প্রায়শই ইউজফুল হলেও, এর একটি বড় সমস্যা আছে। এটি ডেটার একটি ইন্টারপ্রিটেশনকে অন্যগুলোর ওপর প্রিভিলেজ করে। এটি ধরে নেয় x এবং y সবসময় একটি Point হবে এবং আপনাকে কখনো তাদের Cell বা Vector হিসেবে ব্যবহার করতে হবে না।
কী হয় যখন আপনার একটি Cell দরকার? Point ডেটা অয়ন করে। তাই আপনি আপনার সেল মেথডগুলো Point-এ যোগ করুন। সময়ের সাথে সাথে আপনি একটি Point ক্লাস পাবেন যা লুজলি-রিলেটেড মেথডের একটি জাঙ্ক ড্রয়ার (junk drawer)।
Junk-drawer ক্লাসগুলো এত সাধারণ যে আমরা প্রায়শই এদের অনিবার্য হিসেবে মেনে নিই এবং ডেভেলপমেন্টে একটি "রিফ্যাক্টরিং" স্টেপ ট্যাক অন করে দিই। কিন্তু কি হবে যদি আমরা প্রথমেই সমস্যাটি এড়াতে পারি?
ওয়েব ডেভেলপমেন্টের প্রথম নিয়ম হচ্ছে আমরা User ক্লাসের কথা বলি না
যেকোনো ব্যাটল-হার্ডেন্ড প্রোডাকশন Rails অ্যাপের হৃদয়ে একটি গিগান্টিক মনস্টার ক্লাস থাকে যেটি User নামে ডাকা হয়।
এটি সবসময় নিখুঁতভাবে শুরু হয়। আপনি লোকেদের লগইন করতে দিতে চান। আপনাকে তাদের ইউজারনেম এবং পাসওয়ার্ড স্টোর করতে হয়, তাই আপনি একটি ক্লাস তৈরি করুন:
class User
attr_accessor :username, :password, :email, :address
def authenticate!(password)
...
end
end
এটি এত ভালো কাজ করে যে অবশেষে লোকেরা আপনাকে টাকা দিতে চায়। "ঠিক আছে," আমরা বলি "একজন User বেসিকলি একই জিনিস Subscriber, তাই আমরা কয়েকটি অ্যাট্রিবিউট এবং মেথড যোগ করব।"
class User
...
attr_accessor :payment_processor_token, :subscription_plan_id, ...etc
def charge_cc
...
end
end
চমৎকার! এখন আমরা কিছু রিয়েল টাকা বানাচ্ছি! CEO সিদ্ধান্ত নিলেন তারা সেলস টিমের জন্য সহজে SalesForce-এ ইম্পোর্ট করার জন্য ইউজার কন্ট্যাক্ট ইনফো সহ VCard এক্সপোর্ট করতে চান। ভিম চালু করুন:
class User
...
def export_vcard
...
end
end
আমরা কী করলাম?
আমরা একটি User ক্লাস থেকে শুরু করেছিলাম, যার একমাত্র উদ্দেশ্য ছিল অথেন্টিকেশন হ্যান্ডল করা। অতিরিক্ত মেথড এবং অ্যাট্রিবিউট যোগ করে, আমরা এটিকে একটি User/Subscriber/Contact ফ্র্যাঙ্কেনস্টাইন হাইব্রিডে পরিণত করলাম।
আমরা কেন এটা করলাম? কি আমরা ডেভেলপার হিসেবে নিজেদের রেসপেক্ট করি না? আমাদের পেরেন্টস আমাদের পর্যাপ্ত ভালোবেসেন না? অথবা আমরা নিজেদের ফেইল্যুরের জন্য সেটআপ করলাম এই বিশ্বাস দিয়ে যে এই ডেটা বাস্তবে একটি জিনিস, একটি অবজেক্ট?
আমরা কেন বলি username, password এবং ইমেইল অ্যাড্রেসের কম্বিনেশন হচ্ছে একটি User? কেন না Subscriber? অথবা Contact? অথবা SessionHolder?
ডেটা সম্পর্কে সত্য হচ্ছে এটি ডেটা
ডেটা শুধু ডেটা। আজ আপনাকে এটি বয়ফ্রেন্ডের মত ব 다니তে হয়। কাল এটি হতে পারে এক্স-বয়ফ্রেন্ড।
এই অ্যাপ্রোচটিই ফাংশনাল ল্যাঙ্গুয়েজ যেমন Elixir নেয়। ডেটা সিম্পল স্ট্রাকচারে স্টোর থাকে Ruby-এর হ্যাশ, অ্যারে, স্ট্রিং এর মতো। আপনি যখন ডেটার সাথে কিছু করতে চান, আপনি সেটি একটি ফাংশনে পাস করুন। যেকোনো রেজাল্ট সেই ফাংশন থেকে রিটার্ন হয়।
এটি শুনতে সিম্পল, কিন্তু এই অ্যাপ্রোচ কনসার্নগুলো আলাদা মডিউলে আলাদা করা খুব সহজ করে দেয়।
এখানে একটি কার্টুন দেখানো হয়েছে কীভাবে আমরা আমাদের User সিস্টেমটি Elixir-এ কনস্ট্রাক্ট করতে পারি:
my_user = %{username: "foo", password: ..., phone: ..., payment_token: ...}
my_user = Authentication.authenticate(my_user)
my_user = Subscription.charge(my_user)
my_user = Contact.export_vcard(my_user)
কারণ ডেটা কোড থেকে আলাদা, কোনো মডিউল ওই ডেটার প্রিভিলেজড ইন্টারপ্রিটেশন রাখে না।
Ruby-এ ফিরে আনা
যেহেতু এই অ্যাপ্রোচ Elixir-এ এত ভালো কাজ করে, কেন না Ruby-এ আডাপ্ট করি? User-কে এর ডেটার একটি সিম্পল র্যাপারে রূপান্তর করতে এবং সকল বিজনেস লজিক মডিউলে পুল করতে কিছুই বাধা দিচ্ছে না।
class User
attr_accessor :username, :password, :email, :address
end
module Authentication
def self.authenticate!(user)
..
end
end
module Subscription
def self.charge(user)
..
end
end
module Contact
def self.export_vcard(user)
..
end
end
আপনি User ক্লাসকে একটি স্ট্রাক্ট-এও রূপান্তর করতে পারেন, বা এটি (কিন্ড-অফ) ইমমিউটেবল করার কোড যোগ করতে পারেন।
তাহলে কী?
এটি কি একটি טרিভিয়াল চেঞ্জ মনে হয়? পয়েন্ট কী?
যেমন আমরা দেখেছি, টিপিক্যাল OO অ্যাপ্রোচে সমস্যা আছে যেন এটি পুরনো হয়। একটি নির্দিষ্ট ক্লাস অয়ন করে ডেটা বললে, আমরা সমস্যায় পড়ি যখন আমরা সেই ডেটাকে অন্য কোনোভাবে ব্যবহার করতে চাই।
মডুলার অ্যাপ্রোচে, যেভাবেই, বিহেভিয়ার যোগ করা কোনো সমস্যা নয়। আমরা শুধু একটি নতুন মডিউল তৈরি করি যা ডেটাকে যেভাবে ইন্টারপ্রেট করতে চায় সেভাবে করে। এটি করা সহজ কারণ ডেটা এবং ফাংশনালিটি সম্পূর্ণ আলাদা।
অন্যান্য অ্যাপ্রোচ
Junk-drawer সমস্যা প্রিভেন্ট করার অন্যান্য অ্যাপ্রোচও আছে। ট্রেডিশনালি, অবজেক্ট-ওরিয়েন্টেড প্রোগ্রামিং-এ আপনি ইনহেরিটেন্স এবং ক্যারফুল রিফ্যাক্টরিং দিয়ে এটা করার চেষ্টা করবেন। ২০১২ সালে স্যান্ডি মেটজ Practical Object Oriented Design in Ruby পাবলিশ করেছেন যা অনেক মানুষকে কম্পোজিং অবজেক্ট ডিপেন্ডেন্সি ইনজেকশন দিয়ে শুরু করতে কনভিন্স করল। আরো gầnে, ফাংশনাল প্রোগ্রামিংর জনপ্রিয়তা Rubyist-দের ইমমিউটেবল "ডেটা অবজেক্ট" এক্সপেরিমেন্ট করার দিকে ঠেলে দিয়েছে।
এই সব অ্যাপ্রোচ ক্লিন, বিউটিফুল কোড তৈরি করতে ব্যবহার করা যায়। তবে ক্লাস সাধারণত ডেটা অয়ন করে থাকলে, ক্লাস কী এবং ক্লাস ডেটার সাথে কী করে সেটির মধ্যে টেনশন সবসময় থাকবে।
আমি সন্দেহ করি এই অ্যাপ্রোচগুলোর যেসব সাক্সেস জাঙ্ক-ড্রয়ার সমস্যা এড়াতে, তার ফলাফল হচ্ছে ডেটা কোড থেকে ডিকাপলিং।