ক্লাস এবং অবজেক্ট (Classes & Objects)
বিক্ষিপ্ত ডেটা (Scattered Data) থেকে সুসংগঠিত অবজেক্টে (Organized Objects) রূপান্তর
ধরুন, আপনি ব্যাংকিং সম্পর্কিত একটি অ্যাপ (banking app) তৈরি করছেন। এখন আপনি চাইলে এর প্রতিটি গ্রাহককে (customer) ট্র্যাক বা নজরে রাখার জন্য আলাদা আলাদা ভ্যারিয়েবল (separate variables) রাখতে পারেন — যেমন balance1, name1, balance2, name2 ইত্যাদি — কিন্তু এতে করে এটি খুব দ্রুতই একটি জগাখিচুড়ি বা মেসি (messy) অবস্থায় পরিণত হবে। তাই এর সমাধান হিসেবে আপনার মূলত এমন একটি BankAccount (ব্যাংক অ্যাকাউন্ট) ব্লুপ্রিন্টের (blueprint) প্রয়োজন পড়বে যা একই প্যাকেজের (clean package) মধ্যে এর সমস্ত ডেটা (data) (তার ব্যালেন্স বা balance, তার নাম বা name) এবং এর অপারেশনগুলোকে (operations) (ডিপোজিট বা deposit, উইথড্র বা withdraw) একসঙ্গে বান্ডেল (bundles) আকারে রাখতে সাহায্য করবে।
আর এই ব্লুপ্রিন্টটিকেই (blueprint) মূলত একটি ক্লাস বা class বলা হয়। ওই ক্লাসটি থেকে আপনি এরপর যে অ্যাকাউন্টটিই (account) তৈরি করবেন না কেন তাকে মূলত একটি অবজেক্ট (object) (বা ইনস্ট্যান্স বা instance) বলা হবে। এই ক্লাসটিই মূলত এর সমস্ত শর্ত বা নিয়মকানুনগুলোকে (rules) নির্ধারণ করে দেয়; আর এখানকার অবজেক্টগুলো (objects) মূলত একে ভিত্তি করেই বেঁচে থাকে বা কাজ করে।
class (ক্লাস) বনাম struct (স্ট্রাকচার)
সি++ (C++)-এ class এবং struct মূলত প্রায় একই (almost identical) জিনিস। এদের মধ্যে একমাত্র পার্থক্যটি (difference) হলো এদের ডিফল্ট অ্যাক্সেস লেভেলে (default access level):
struct— এখানকার মেম্বারগুলো (members) মূলত ডিফল্টভাবেই (by default) পাবলিক (public) হয়ে থাকেclass— এখানকার মেম্বারগুলো (members) মূলত ডিফল্টভাবেই (by default) প্রাইভেট (private) হয়ে থাকে
প্রথাগত বা সাধারণ নিয়ম (convention) অনুযায়ী, শুধু সাধারণ কোনো ডেটার বান্ডেলের (data bundles) ক্ষেত্রে মূলত (যেমন এক্স বা x ও ওয়াই বা y স্থানাঙ্কের কোনো পয়েন্ট বা Point) struct ব্যবহার করুন, আর যেকোনো ধরনের বিহেভিয়ার বা আচরণ (behavior) এবং ইনভ্যারিয়েন্টের (invariants) ক্ষেত্রে মূলত class-টিকে ব্যবহার করুন।
struct বনাম class — একমাত্র আসল পার্থক্যটি (The Only Real Difference)
কনস্ট্রাক্টরগুলো (Constructors) — অবজেক্টটিকে (Object) ঠিকভাবে তৈরি করা (Building the Object Right)
যেকোনো কনস্ট্রাক্টর (constructor) হলো মূলত একটি স্পেশাল ফাংশন (special function) যা একটি অবজেক্ট (object) তৈরি হওয়ার সময়ই রান (runs) করে। এর প্রধান কাজটিই হলো ওই অবজেক্টটিকে একেবারে শুরু (beginning) থেকেই একটি বৈধ বা ভ্যালিড অবস্থায় (valid state) নিয়ে আসা। যাতে করে অর্ধেকভাবে ইনিশিয়ালাইজ (half-initialized) করা কোনো অবজেক্ট (objects) বাইরে আনমনে ঘুরে বেড়াতে না পারে।
সি++ (C++) মূলত এক্ষেত্রে আপনাকে বেশ কয়েক ধরনের কনস্ট্রাক্টরের স্বাদ (flavors) নেওয়ার সুযোগ দেয়:
- ডিফল্ট কনস্ট্রাক্টর (Default constructor) — এর কোনো প্যারামিটার (parameters) নেই, এটি স্বাভাবিকভাবেই একটি বুদ্ধিমান এবং ডিফল্ট (sensible default) কনস্ট্রাক্টর তৈরি করে
- প্যারামিটারাইজড কনস্ট্রাক্টর (Parameterized constructor) — এটি নিজে থেকেই বিভিন্ন প্যারামিটারের মানগুলোকে (specific values) যুক্ত বা ইনিশিয়ালাইজ (initialize) করার জন্য আর্গুমেন্ট (arguments) নিয়ে থাকে
- মেম্বার ইনিশিয়ালাইজার লিস্ট (Member initializer list) — এটি মূলত কনস্ট্রাক্টরের বডিটি (constructor body) রান (runs) করার আগেই এর মেম্বারগুলোকে (members) মান যুক্ত বা ইনিশিয়ালাইজ (initializes) করে দেয় (এটি অনেক ফাস্ট বা faster, এবং বেশিরভাগ const/reference মেম্বারগুলোর জন্য এটি দরকার বা required হয়)
BankAccount — একটি পূর্ণাঙ্গ বা কমপ্লিট ক্লাস (A Complete Class)
: owner(name), balance(initial) লিখলে, এটি প্রত্যেকটি মেম্বারকে (member) সরাসরি ইনিশিয়ালাইজ (initializes) করে দেয়। কিন্তু এর বদলে বডিতে (body) অ্যাসাইন করলে (যেমন owner = name;) এটি প্রথমে মেম্বারটিকে (member) ডিফল্ট-কনস্ট্রাক্ট (default-constructs) করে এবং এরপর অ্যাসাইন (assigns) করে — ফলে এটি মূলত ওই কাজটিকে ডাবল বা দ্বিগুণ (double work) করে দেয়। তবে const এবং রেফারেন্স মেম্বারগুলোর (reference members) ক্ষেত্রে অবশ্যই ইনিশিয়ালাইজার লিস্ট (initializer lists) ব্যবহার করা বাধ্যতামূলক বা required।অ্যাক্সেস মডিফায়ার (Access Modifiers) — এখানকার দরজাগুলোকে নিয়ন্ত্রণ (Controlling the Doors) করা
যেকোনো ক্লাসের (class) প্রতিটি মেম্বারেরই (member) একটি নির্দিষ্ট অ্যাক্সেস লেভেল (access level) থাকে:
- পাবলিক (public) — এটিকে মূলত যে কেউ বা যেখান থেকেই খুশি সেখান থেকেই অ্যাক্সেস (access) করতে পারবে। এটি মূলত এমন একটি ইন্টারফেস (interface) যাকে আপনি বাইরের জগতের (world) সামনে তুলে (expose) ধরছেন।
- প্রাইভেট (private) — এটিকে শুধুমাত্র ক্লাসটি (class) নিজেই অ্যাক্সেস (access) করতে পারবে। এটি মূলত ইন্টারনাল ডেটা (internal data) এবং যেকোনো হেল্পার ফাংশনের (helper functions) জন্য ব্যবহার করা হয়।
- প্রোটেক্টেড (protected) — এক্ষেত্রে এখানকার মূল ক্লাস (class) এবং এর চাইল্ড বা ডেরাইভড (derived) ক্লাসগুলো এটিকে অ্যাক্সেস (access) করতে পারবে। মূলত ইনহেরিটেন্সগুলোর (inheritance) ক্ষেত্রে আমরা এটি আরও ব্যাপকভাবে দেখতে পাব।
এখানকার সোনার নিয়মটি (golden rule) হলো: ডেটাগুলোকে (data) মূলত প্রাইভেট (private) করে রাখা এবং সেগুলোর সাথে কাজ করার জন্য কিছু পাবলিক মেথড (public methods) প্রোভাইড বা প্রদান করা। একে এনক্যাপসুলেশন (encapsulation) বলা হয় — যেখানে যেকোনো একটি ক্লিন বা পরিপাটি ইন্টারফেসের (clean interface) পেছনে সমস্ত অগোছালো ইন্টারনাল বা অভ্যন্তরীণ (messy internals) জিনিসগুলোকে লুকিয়ে রাখা যায়।
এই this পয়েন্টারটি (Pointer)
যেকোনো নন-স্ট্যাটিক মেথডের (non-static method) ভেতরে, this হলো মূলত বর্তমান অবজেক্টের (current object) দিকে নির্দেশ করা একটি পয়েন্টার (pointer)। আপনার হয়তো সরাসরিভাবে (explicitly) এর প্রয়োজন খুব একটা পড়বে না, কিন্তু যখন কোনো প্যারামিটারের নাম (parameter name) আপনার মেম্বারের নামটিকে (member name) শ্যাডো বা ঢেকে (shadows) দেয়, বা যখন আপনি সরাসরি ওই আসল অবজেক্টটিকেই (object) রিটার্ন (return) করতে চান (যেমন মেথড চেইনিং (method chaining)-এর ক্ষেত্রে), তখন এটি বেশ কার্যকর (useful) হতে পারে।
this পয়েন্টার (this Pointer) এবং মেথড চেইনিং (Method Chaining)
const মেথডগুলো (const Methods) — রিড-ওনলি বা শুধুমাত্র পাঠযোগ্য থাকার প্রতিশ্রুতি (The Read-Only Promise)
কোনো একটি মেথডকে (method) const হিসেবে মার্ক বা চিহ্নিত (Marking) করে রাখার অর্থ হলো কম্পাইলারকে (compiler) এটি বলা যে: "এই মেথডটি এর পেছনের কোনো মেম্বার ভ্যারিয়েবলকেই (member variables) মডিফাই বা পরিবর্তন (modify) করবে না।" এটি একটি অত্যন্ত গুরুত্বপূর্ণ বা ক্রুশিয়াল (crucial) কাজ, কারণ এটি আপনাকে সরাসরি const অবজেক্টগুলোতে (objects) এবং const রেফারেন্সগুলোতে (references) এই মেথডটিকে (method) কল করার অনুমতি দেয়। যেমন আপনার কাছে যদি একটি const BankAccount& থাকে, তবে আপনি নিশ্চিন্তে এর getBalance() (const) মেথডটিকে কল করতে পারবেন, কিন্তু deposit() (non-const) মেথডটিকে কখনোই নয়।
ডেস্ট্রাক্টরগুলো (Destructors) — ক্লিন আপ বা পরিষ্কার (Cleaning Up) করা
যখন কোনো অবজেক্ট ডেস্ট্রয় বা ধ্বংস (destroyed) হয়ে যায় (যেমন স্কোপের বা scope-এর বাইরে চলে গেলে, ডিলিট বা deleted হয়ে গেলে ইত্যাদি), তখন একটি ডেস্ট্রাক্টর (destructor) স্বয়ংক্রিয়ভাবে (automatically) রান বা চলতে শুরু করে। এর নামটি হলো ~ClassName()। কোনো সাধারণ ক্লাসের (simple classes) ক্ষেত্রে, কম্পাইলারের (compiler-generated) সাহায্যে জেনারেট হওয়া ডেস্ট্রাক্টরটিই মূলত যথেষ্ঠ। শুধুমাত্র ওই ক্লাসগুলো (class) যদি বিভিন্ন হিপ মেমরি (heap memory), ফাইল হ্যান্ডেলগুলো (file handles), বা নেটওয়ার্ক কানেকশনের (network connections) মতো রিসোর্সগুলোকে (resources) স্বয়ংক্রিয়ভাবে ম্যানেজ (manages) করতে চায়, ঠিক তখনই আপনার নিজস্ব (own) ডেস্ট্রাক্টর লেখার প্রয়োজন পড়ে।