আগের পর্বে আমরা Promise বিস্তারিতভাবে শিখেছিলাম। এই পর্ব থেকে আমরা Object-Oriented Programming (OOP)-এ ঢুকছি — শুরুতেই Class আর Constructor, যা দিয়ে একই ধরনের একাধিক অবজেক্ট সুশৃঙ্খলভাবে তৈরি করা যায়।
সমস্যাটা কী?
ধরো একটা বইয়ের সিস্টেমে একাধিক বই অবজেক্ট বানাতে হবে — প্রতিটাতে একই ধরনের প্রপার্টি আর মেথড থাকবে। প্লেইন অবজেক্ট দিয়ে বারবার একই কাঠামো লেখা বিরক্তিকর আর ভুলপ্রবণ:
// ❌ প্রতিটা বই আলাদা করে এভাবে লিখতে হচ্ছে — বারবার একই স্ট্রাকচার
const book1 = {
title: "সোনার তরী",
author: "রবীন্দ্রনাথ ঠাকুর",
views: 0,
incrementViews() {
this.views++;
}
};
const book2 = {
title: "পথের পাঁচালী",
author: "বিভূতিভূষণ",
views: 0,
incrementViews() {
this.views++;
}
};
Class দিয়ে সমাধান
Class একটা "নকশা" (blueprint) — এখান থেকে একই কাঠামোর একাধিক অবজেক্ট (instance) সহজে তৈরি করা যায়।
class Book {
constructor(title, author) {
this.title = title;
this.author = author;
this.views = 0;
}
incrementViews() {
this.views++;
}
getInfo() {
return `${this.title} — ${this.author} (${this.views} ভিউ)`;
}
}
const book1 = new Book("সোনার তরী", "রবীন্দ্রনাথ ঠাকুর");
const book2 = new Book("পথের পাঁচালী", "বিভূতিভূষণ");
book1.incrementViews();
book1.incrementViews();
console.log(book1.getInfo()); // সোনার তরী — রবীন্দ্রনাথ ঠাকুর (2 ভিউ)
console.log(book2.getInfo()); // পথের পাঁচালী — বিভূতিভূষণ (0 ভিউ)
Constructor কী করে?
constructor একটা বিশেষ মেথড যেটা new কীওয়ার্ড দিয়ে নতুন অবজেক্ট বানানোর সময় স্বয়ংক্রিয়ভাবে কল হয়। এখানে যা যা করা হয়:
| ধাপ | কী ঘটে |
|---|---|
| ১ | new Book(...) কল হলে একটা নতুন খালি অবজেক্ট তৈরি হয় |
| ২ | সেই অবজেক্টই constructor-এর ভেতরে this হয়ে যায় |
| ৩ | this.title = title-এর মতো লাইনগুলো প্রপার্টি বসিয়ে দেয় |
| ৪ | সম্পূর্ণ তৈরি হওয়া অবজেক্টটা স্বয়ংক্রিয়ভাবে রিটার্ন হয় |
Method-গুলো কোথায় থাকে?
একটা গুরুত্বপূর্ণ বিষয় — ক্লাসের মেথড (incrementViews, getInfo) প্রতিটা instance-এ আলাদা করে কপি হয় না — এগুলো Book.prototype-এ একবারই থাকে, সব instance সেটা শেয়ার করে।
console.log(book1.incrementViews === book2.incrementViews); // true // দুটো ভিন্ন বই অবজেক্ট, কিন্তু মেথড একটাই — মেমরি বাঁচে
এই কারণেই Class instance-ভিত্তিক ডেটা (title, views) মেমরি-এফিশিয়েন্টভাবে সামলাতে পারে, প্লেইন অবজেক্ট লিটারেল বারবার বানানোর চেয়ে ভালো।
Getter ও Setter
ক্লাসে get/set দিয়ে প্রপার্টির মতো দেখতে কিন্তু আসলে ফাংশন-চালিত অ্যাক্সেসর বানানো যায় — যেমন একটা "কম্পিউটেড" প্রপার্টি।
class Book {
constructor(title, author, pageCount) {
this.title = title;
this.author = author;
this._pageCount = pageCount;
}
// Getter — মেথড কল না করে যেন প্রপার্টির মতোই পড়া যায়
get readingTime() {
return Math.ceil(this._pageCount / 20); // ধরি মিনিটে ২০ পাতা পড়া যায়
}
// Setter — মান বসানোর সময় যাচাই করা যায়
set pageCount(value) {
if (value < 0) {
throw new Error("পাতা সংখ্যা ঋণাত্মক হতে পারে না");
}
this._pageCount = value;
}
}
const book = new Book("সোনার তরী", "রবীন্দ্রনাথ ঠাকুর", 120);
console.log(book.readingTime); // 6 — মেথডের মতো () ছাড়াই কল হচ্ছে
book.pageCount = 200; // setter কল হচ্ছে
console.log(book.readingTime); // 10
Static মেথড
static মেথড কোনো instance-এ না, ক্লাসের উপরেই সরাসরি কল হয় — এটা এমন হেল্পার ফাংশনের জন্য উপযুক্ত যা কোনো নির্দিষ্ট instance-এর ডেটা লাগে না।
class Book {
constructor(title, author) {
this.title = title;
this.author = author;
}
// static মেথড — একটা রেডিমেড ফরম্যাট থেকে Book বানায়
static fromString(str) {
const [title, author] = str.split(" - ");
return new Book(title, author);
}
}
// instance বানানো ছাড়াই সরাসরি ক্লাসের উপর কল
const book = Book.fromString("সোনার তরী - রবীন্দ্রনাথ ঠাকুর");
console.log(book.title); // সোনার তরী
Private ফিল্ড (#)
আধুনিক JavaScript-এ # দিয়ে সত্যিকারের প্রাইভেট প্রপার্টি বানানো যায় — এগুলো ক্লাসের বাইরে থেকে একদমই অ্যাক্সেস করা যায় না (আগে যেটা Closure দিয়ে সিমুলেট করতাম, পর্ব ১-এ)।
class BankAccount {
#balance; // প্রাইভেট ফিল্ড ঘোষণা
constructor(initialBalance) {
this.#balance = initialBalance;
}
deposit(amount) {
this.#balance += amount;
}
getBalance() {
return this.#balance;
}
}
const account = new BankAccount(1000);
account.deposit(500);
console.log(account.getBalance()); // 1500
console.log(account.#balance); // ❌ SyntaxError — বাইরে থেকে একদমই অ্যাক্সেস করা যায় না
লাইভ ডেমো — বুক ক্লাস দিয়ে ভিউ কাউন্টার
নিচে দুটো "বই" instance আছে, প্রতিটা একটা Class থেকে বানানো — একটার ভিউ বাড়ালে আরেকটার উপর কোনো প্রভাব পড়ে না।
সোনার তরী
ভিউ: 0
পথের পাঁচালী
ভিউ: 0
একটা সম্পূর্ণ উদাহরণ — বই ম্যানেজার
চলো Constructor, getter, static মেথড, আর private ফিল্ড মিলিয়ে একটা বাস্তব "বুক লাইব্রেরি" ক্লাস বানাই।
class Library {
#books = []; // প্রাইভেট — বাইরে থেকে সরাসরি পাল্টানো যাবে না
addBook(title, author) {
this.#books.push({ title, author, id: Date.now() });
}
removeBook(id) {
this.#books = this.#books.filter((book) => book.id !== id);
}
get totalBooks() {
return this.#books.length;
}
get allBooks() {
return [...this.#books]; // spread দিয়ে কপি — আসল অ্যারে বাইরে থেকে পাল্টানো যাবে না
}
static createWithDefaults() {
const lib = new Library();
lib.addBook("সোনার তরী", "রবীন্দ্রনাথ ঠাকুর");
lib.addBook("পথের পাঁচালী", "বিভূতিভূষণ");
return lib;
}
}
const myLibrary = Library.createWithDefaults();
console.log(myLibrary.totalBooks); // 2
myLibrary.addBook("চোখের বালি", "রবীন্দ্রনাথ ঠাকুর");
console.log(myLibrary.totalBooks); // 3
console.log(myLibrary.allBooks); // সব বইয়ের অ্যারে
এখানে #books সম্পূর্ণ সুরক্ষিত, আর get allBooks() একটা কপি রিটার্ন করে (আগের পর্বের Spread টেকনিক) — এতে বাইরে থেকে কেউ সরাসরি myLibrary.allBooks.push(...) করলেও আসল #books অপরিবর্তিত থাকে।
নিজে চেষ্টা করো
Exercise:
- একটা
Userক্লাস বানাও — নাম, ইমেইল প্রপার্টি, আর একটাget initials()getter যেটা নামের প্রথম অক্ষর রিটার্ন করবে। - উপরের
Libraryক্লাসে একটাfindByAuthor(author)মেথড যোগ করো। BankAccountক্লাসে একটাwithdraw(amount)মেথড যোগ করো, যেটা ব্যালেন্সের বেশি টাকা তুলতে দিলে এরর থ্রো করবে।- একটা static মেথড
Book.compareViews(bookA, bookB)বানাও যেটা কোন বইয়ের ভিউ বেশি সেটা রিটার্ন করবে।
পরের পর্বে যা থাকছে
পরের পর্বে আমরা শিখব OOP — Inheritance — কীভাবে একটা ক্লাস আরেকটা ক্লাসের বৈশিষ্ট্য উত্তরাধিকারসূত্রে পায় (extends, super), যাতে কোড পুনর্ব্যবহার আরও সহজ হয়।