برنامهنویسی شیءگرا (OOP) در جاوااسکریپت
بهنام نوبخت · ۱۴۰۵/۷/۱۱
در مقالههای قبلی با Objectها (اشیاء) آشنا شدیم و یاد گرفتیم چگونه اطلاعات و رفتارهای مرتبط را در یک ساختار مشخص قرار دهیم. اما زمانی که یک پروژه بزرگتر میشود، تعداد Objectها و کدهای مرتبط با آنها افزایش پیدا میکند و مدیریت برنامه دشوارتر میشود.
در چنین شرایطی، برنامهنویسی شیءگرا یا Object-Oriented Programming که به اختصار OOP نامیده میشود به ما کمک میکند کدها را منظمتر، قابل استفاده مجدد و قابل توسعه طراحی کنیم.
جاوااسکریپت نیز از برنامهنویسی شیءگرا پشتیبانی میکند و یکی از مهمترین ابزارهای آن، class است.
در این مقاله ابتدا مفهوم OOP را به زبان ساده یاد میگیریم و سپس با class، constructor، متدها، this، ساخت Instance، وراثت و چند مفهوم مهم دیگر کار خواهیم کرد.
OOP چیست؟
OOP یا Object-Oriented Programming روشی برای طراحی و نوشتن برنامه است که در آن اطلاعات و رفتارهای مرتبط را در قالب Objectها سازماندهی میکنیم.
برای مثال فرض کنید در حال ساخت یک فروشگاه اینترنتی هستیم.
هر محصول میتواند اطلاعاتی مانند موارد زیر داشته باشد:
Product
├── name
├── price
├── category
└── getInfo()در این مثال:
nameنام محصول است.priceقیمت محصول است.categoryدستهبندی محصول است.getInfo()رفتاری است که اطلاعات محصول را برمیگرداند.
در یک پروژه واقعی ممکن است صدها یا هزاران محصول داشته باشیم. OOP کمک میکند ساختار این اطلاعات و رفتارها منظم باقی بماند.
چرا از OOP استفاده میکنیم؟
فرض کنید بخواهیم اطلاعات چند کاربر را بدون استفاده از Class ایجاد کنیم:
const user1 = {
name: "Ali",
age: 25
};
const user2 = {
name: "Sara",
age: 30
};
const user3 = {
name: "Reza",
age: 28
};این روش کاملاً درست است.
اما اگر هر کاربر علاوه بر اطلاعات، رفتارهای مختلفی هم داشته باشد، مدیریت کد میتواند سختتر شود.
برای مثال:
login()
logout()
changePassword()
getProfile()در چنین شرایطی میتوانیم یک الگوی مشخص برای ساخت Userها ایجاد کنیم.
اینجاست که class وارد میشود.
Class چیست؟
class را میتوانیم به عنوان یک الگو یا قالب برای ساخت Objectها در نظر بگیریم.
برای مثال:
class User {
}در اینجا یک Class به نام User ساختهایم.
اما هنوز هیچ کاربر واقعی ایجاد نکردهایم.
میتوانیم از این Class برای ساخت Objectهای مختلف استفاده کنیم.
ساخت اولین Class
یک مثال ساده:
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
}در اینجا Class ما دو ویژگی دارد:
name
ageاما این ویژگیها زمانی مقدار میگیرند که یک Object از Class بسازیم.
Constructor چیست؟
constructor یک متد ویژه داخل Class است که هنگام ایجاد یک Instance جدید به صورت خودکار اجرا میشود.
مثلاً:
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
}اگر بنویسیم:
const user1 = new User("Ali", 25);متد constructor به صورت خودکار اجرا میشود.
در نتیجه:
user1.nameبرابر است با:
Aliو:
user1.ageبرابر است با:
25new چیست؟
برای ساخت یک Object جدید از یک Class معمولاً از new استفاده میکنیم.
مثال:
const user1 = new User("Ali", 25);در اینجا:
UserClass است و:
user1یک Instance از آن Class است.
میتوانیم چند Instance مختلف بسازیم:
const user1 = new User("Ali", 25);
const user2 = new User("Sara", 30);
const user3 = new User("Reza", 28);هر سه Object از یک Class ساخته شدهاند، اما اطلاعات متفاوتی دارند.
Instance چیست؟
به Objectی که از یک Class ساخته میشود، Instance میگوییم.
مثلاً:
class User {
constructor(name) {
this.name = name;
}
}
const user1 = new User("Ali");
const user2 = new User("Sara");در این مثال:
User = Class
user1 = Instance
user2 = Instanceهر دو Instance از Class User ساخته شدهاند.
this در Class
یکی از مهمترین مفاهیم هنگام کار با Classها، this است.
در مثال زیر:
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
}عبارت:
this.nameبه ویژگی name مربوط به همان Instance اشاره میکند.
برای مثال:
const user1 = new User("Ali", 25);
const user2 = new User("Sara", 30);برای user1:
this.nameبه:
Aliاشاره میکند.
برای user2:
this.nameبه:
Saraاشاره میکند.
به زبان ساده، میتوان گفت:
thisدر اینجا به Object فعلی اشاره میکند.
اضافه کردن Method به Class
Class فقط برای نگهداری اطلاعات نیست.
میتوانیم رفتارهای مختلف را نیز در آن تعریف کنیم.
مثلاً:
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
introduce() {
console.log(`من ${this.name} هستم و ${this.age} سال دارم.`);
}
}اکنون میتوانیم:
const user1 = new User("Ali", 25);
user1.introduce();خروجی:
من Ali هستم و 25 سال دارم.Method چیست؟
Method در واقع تابعی است که به یک Object یا Class مرتبط است.
در Class:
introduce() {
console.log("Hello");
}یک Method است.
میتوانیم چند Method داشته باشیم:
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
introduce() {
console.log(`Hello, I am ${this.name}`);
}
getAge() {
return this.age;
}
}استفاده:
const user = new User("Ali", 25);
user.introduce();
console.log(user.getAge());خروجی:
Hello, I am Ali
25Method دارای پارامتر
Methodها میتوانند پارامتر نیز داشته باشند.
مثلاً:
class Calculator {
add(a, b) {
return a + b;
}
}استفاده:
const calculator = new Calculator();
console.log(calculator.add(10, 20));خروجی:
30تغییر اطلاعات Instance
میتوانیم اطلاعات یک Instance را تغییر دهیم.
مثال:
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
const user = new User("Ali", 25);
console.log(user.age);
user.age = 26;
console.log(user.age);خروجی:
25
26تغییر اطلاعات از طریق Method
میتوانیم این کار را از طریق یک Method نیز انجام دهیم:
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
increaseAge() {
this.age++;
}
}استفاده:
const user = new User("Ali", 25);
user.increaseAge();
console.log(user.age);خروجی:
26این روش در پروژههای بزرگ میتواند به سازماندهی بهتر رفتار Object کمک کند.
Constructor با مقدار پیشفرض
مانند توابع، Constructor نیز میتواند مقدار پیشفرض داشته باشد.
مثال:
class User {
constructor(name, role = "user") {
this.name = name;
this.role = role;
}
}اگر نقش را مشخص نکنیم:
const user = new User("Ali");
console.log(user.role);خروجی:
userاما اگر مقدار را مشخص کنیم:
const admin = new User("Sara", "admin");
console.log(admin.role);خروجی:
adminساخت چند Instance
یکی از مزایای مهم Class این است که میتوانیم از یک قالب، Objectهای زیادی ایجاد کنیم.
مثال:
class Product {
constructor(name, price) {
this.name = name;
this.price = price;
}
getInfo() {
return `${this.name} - ${this.price} تومان`;
}
}حالا:
const product1 = new Product("Laptop", 50000000);
const product2 = new Product("Phone", 30000000);
const product3 = new Product("Mouse", 1000000);هر سه Object از یک Class ساخته شدهاند.
console.log(product1.getInfo());
console.log(product2.getInfo());
console.log(product3.getInfo());خروجی:
Laptop - 50000000 تومان
Phone - 30000000 تومان
Mouse - 1000000 تومانمثال عملی: کلاس Product
حالا یک مثال کمی کاملتر ایجاد کنیم.
class Product {
constructor(name, price, category) {
this.name = name;
this.price = price;
this.category = category;
}
getInfo() {
return `${this.name} - ${this.price} تومان`;
}
applyDiscount(percent) {
this.price = this.price - (this.price * percent / 100);
}
}ساخت محصول:
const product = new Product(
"Laptop",
50000000,
"Electronics"
);نمایش اطلاعات:
console.log(product.getInfo());اعمال تخفیف:
product.applyDiscount(10);
console.log(product.price);قیمت جدید:
45000000در این مثال، اطلاعات و رفتار مربوط به محصول در یک ساختار قرار گرفتهاند.
مثال عملی: کلاس BankAccount
یک مثال دیگر:
class BankAccount {
constructor(owner, balance = 0) {
this.owner = owner;
this.balance = balance;
}
deposit(amount) {
this.balance += amount;
}
withdraw(amount) {
if (amount <= this.balance) {
this.balance -= amount;
} else {
console.log("موجودی کافی نیست.");
}
}
getBalance() {
return this.balance;
}
}استفاده:
const account = new BankAccount("Ali", 1000000);
account.deposit(500000);
console.log(account.getBalance());خروجی:
1500000برداشت:
account.withdraw(300000);
console.log(account.getBalance());خروجی:
1200000اگر مبلغ برداشت بیشتر از موجودی باشد:
account.withdraw(5000000);خروجی:
موجودی کافی نیست.این مثال نشان میدهد که یک Class میتواند هم داده و هم رفتار مرتبط با داده را مدیریت کند.
بررسی نوع Instance با instanceof
جاوااسکریپت عملگر instanceof را برای بررسی ارتباط یک Object با یک Class در اختیار ما قرار میدهد.
مثال:
class User {
constructor(name) {
this.name = name;
}
}
const user = new User("Ali");
console.log(user instanceof User);خروجی:
trueاما:
const product = {
name: "Laptop"
};
console.log(product instanceof User);خروجی:
falseبنابراین:
object instanceof Classمیتواند بررسی کند که Object موردنظر Instance آن Class هست یا خیر.
وراثت در جاوااسکریپت
یکی از مفاهیم مهم OOP، Inheritance یا وراثت است.
فرض کنید یک Class عمومی به نام User داریم:
class User {
constructor(name) {
this.name = name;
}
login() {
console.log(`${this.name} وارد سیستم شد.`);
}
}حالا میخواهیم یک نوع خاصتر از User ایجاد کنیم؛ مثلاً Admin.
میتوانیم از extends استفاده کنیم.
class Admin extends User {
}در این حالت Admin از User ارثبری میکند.
حالا:
const admin = new Admin("Ali");
admin.login();خروجی:
Ali وارد سیستم شد.با اینکه Method login() را داخل Admin تعریف نکردهایم، از Class والد یعنی User به ارث رسیده است.
extends چیست؟
کلمه کلیدی:
extendsبرای ایجاد وراثت بین Classها استفاده میشود.
ساختار کلی:
class Child extends Parent {
}برای مثال:
class Admin extends User {
}در اینجا:
User → Parent Class
Admin → Child Classاستفاده از super
اگر Class فرزند Constructor مخصوص خودش را داشته باشد، معمولاً باید Constructor مربوط به Class والد را نیز با super() فراخوانی کنیم.
مثال:
class User {
constructor(name) {
this.name = name;
}
}
class Admin extends User {
constructor(name, level) {
super(name);
this.level = level;
}
}ساخت Admin:
const admin = new Admin("Ali", 1);
console.log(admin.name);
console.log(admin.level);خروجی:
Ali
1در اینجا:
super(name);Constructor مربوط به User را اجرا میکند.
Override کردن Method
Class فرزند میتواند یک Method موجود در Class والد را با پیادهسازی جدید خودش تغییر دهد.
مثال:
class User {
introduce() {
console.log("I am a user.");
}
}
class Admin extends User {
introduce() {
console.log("I am an admin.");
}
}حالا:
const user = new User();
const admin = new Admin();
user.introduce();
admin.introduce();خروجی:
I am a user.
I am an admin.در اینجا Method introduce() در Class Admin بازنویسی شده است.
به این مفهوم Method Overriding گفته میشود.
Private Fields در جاوااسکریپت
جاوااسکریپت امکان ایجاد فیلدهای خصوصی را نیز دارد.
برای این کار از علامت # استفاده میکنیم.
مثال:
class User {
#password;
constructor(username, password) {
this.username = username;
this.#password = password;
}
checkPassword(password) {
return this.#password === password;
}
}حالا:
const user = new User("Ali", "12345");میتوانیم از Method استفاده کنیم:
console.log(user.checkPassword("12345"));خروجی:
trueاما نمیتوانیم مستقیماً به فیلد خصوصی دسترسی پیدا کنیم:
console.log(user.#password);این کد باعث خطا میشود.
علامت # باعث میشود این فیلد فقط داخل همان Class قابل دسترسی باشد.
در پروژههای واقعی، نگهداری رمز عبور به این شکل به معنی پیادهسازی امن سیستم احراز هویت نیست. سیستمهای واقعی باید از روشهای استاندارد Hash کردن و مدیریت امن رمز عبور استفاده کنند.
Static Method چیست؟
گاهی یک Method به Instance خاصی وابسته نیست و مستقیماً به خود Class مربوط میشود.
در چنین شرایطی میتوانیم از static استفاده کنیم.
مثال:
class Calculator {
static add(a, b) {
return a + b;
}
}در این حالت لازم نیست Object بسازیم:
console.log(Calculator.add(10, 20));خروجی:
30اما این روش درست نیست:
const calculator = new Calculator();
calculator.add(10, 20);زیرا add یک Static Method است و به Instance تعلق ندارد.
برای شروع کافی است این تفاوت را به خاطر بسپارید:
Instance Method → استفاده از Object
Static Method → استفاده از Classجزئیات بیشتر درباره static و سایر ویژگیهای پیشرفتهتر Classها را میتوان در مباحث پیشرفته جاوااسکریپت بررسی کرد.
تفاوت Object و Class
درک این تفاوت بسیار مهم است.
Object
Object یک نمونه واقعی از اطلاعات است:
const user = {
name: "Ali",
age: 25
};Class
Class یک قالب برای ساخت Objectهای مشابه است:
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
}سپس:
const user1 = new User("Ali", 25);
const user2 = new User("Sara", 30);بنابراین میتوان گفت:
Class
↓
Template
Instance
↓
Actual Objectتفاوت Function و Method
تابع معمولی:
function add(a, b) {
return a + b;
}اما تابعی که داخل Class تعریف شده باشد، Method محسوب میشود:
class Calculator {
add(a, b) {
return a + b;
}
}استفاده:
const calculator = new Calculator();
console.log(calculator.add(10, 20));یک مثال کاملتر
حالا یک Class برای مدیریت کاربران ایجاد کنیم:
class User {
constructor(name, age, role = "user") {
this.name = name;
this.age = age;
this.role = role;
}
introduce() {
return `نام: ${this.name} - سن: ${this.age}`;
}
isAdult() {
return this.age >= 18;
}
changeRole(newRole) {
this.role = newRole;
}
}ایجاد User:
const user = new User("Ali", 25);نمایش اطلاعات:
console.log(user.introduce());بررسی سن:
console.log(user.isAdult());خروجی:
trueتغییر نقش:
user.changeRole("admin");
console.log(user.role);خروجی:
adminدر این مثال، یک User هم اطلاعات دارد و هم رفتارهای مربوط به خودش.
OOP و پروژههای واقعی
در پروژههای واقعی ممکن است Classهایی مانند موارد زیر داشته باشیم:
User
Product
Order
Payment
Category
Company
Advertisement
Notificationبرای مثال در یک فروشگاه اینترنتی:
Product
├── name
├── price
├── category
└── applyDiscount()
Order
├── items
├── total
└── calculateTotal()
User
├── name
├── email
└── login()این ساختار میتواند به برنامهنویس کمک کند بخشهای مختلف برنامه را بهتر سازماندهی کند.
البته در پروژههای واقعی همیشه لازم نیست برای هر چیز یک Class ایجاد کنیم. استفاده بیش از حد از OOP نیز میتواند باعث پیچیدگی غیرضروری شود.
چند مفهوم اصلی OOP
چهار مفهوم مشهور در برنامهنویسی شیءگرا عبارتاند از:
Encapsulation
کپسولهسازی یعنی داده و رفتار مرتبط را در یک ساختار مشخص قرار دهیم و در صورت نیاز دسترسی به بعضی اطلاعات را کنترل کنیم.
مثال:
class BankAccount {
#balance;
constructor(balance) {
this.#balance = balance;
}
getBalance() {
return this.#balance;
}
}در اینجا balance به صورت خصوصی تعریف شده است.
Inheritance
وراثت یعنی یک Class بتواند ویژگیها و رفتارهای Class دیگری را دریافت کند.
مثال:
class Admin extends User {
}Polymorphism
چندریختی یعنی Objectهای مختلف بتوانند یک Method با نام مشابه را به شکل متفاوت اجرا کنند.
مثال:
class User {
introduce() {
console.log("I am a user.");
}
}
class Admin extends User {
introduce() {
console.log("I am an admin.");
}
}هر دو Class دارای:
introduce()هستند، اما رفتار آنها متفاوت است.
Abstraction
انتزاع یعنی جزئیات غیرضروری را از استفادهکننده پنهان کنیم و فقط بخش موردنیاز را در اختیار او قرار دهیم.
برای مثال:
account.withdraw(100000);استفادهکننده فقط Method را فراخوانی میکند و لازم نیست بداند تمام مراحل داخلی بررسی برداشت چگونه انجام میشود.
اشتباهات رایج در OOP
۱. فراموش کردن new
اگر Class داشته باشیم:
class User {
constructor(name) {
this.name = name;
}
}این کد صحیح است:
const user = new User("Ali");اما:
const user = User("Ali");صحیح نیست.
۲. نوشتن constructor با نام اشتباه
نام Constructor باید دقیقاً:
constructorباشد.
مثلاً:
class User {
Constructor(name) {
this.name = name;
}
}اشتباه است.
جاوااسکریپت به حروف بزرگ و کوچک حساس است.
۳. فراموش کردن this
مثلاً:
class User {
constructor(name) {
name = name;
}
}این کد ویژگی name را روی Object ایجاد نمیکند.
روش صحیح:
class User {
constructor(name) {
this.name = name;
}
}۴. اشتباه گرفتن Static Method با Instance Method
اگر داشته باشیم:
class Calculator {
static add(a, b) {
return a + b;
}
}باید بنویسیم:
Calculator.add(10, 20);نه:
const calculator = new Calculator();
calculator.add(10, 20);۵. استفاده بیش از حد از وراثت
هر زمانی که دو Class شبیه یکدیگر هستند، الزاماً نباید از extends استفاده کنیم.
گاهی یک ساختار سادهتر یا استفاده از Object و Function میتواند مناسبتر باشد.
هدف OOP این نیست که کد را پیچیده کنیم؛ هدف آن سازماندهی بهتر کد است.
تمرینهای مقاله
تمرین ۱ — Class ساده
یک Class به نام Student ایجاد کنید که دارای ویژگیهای زیر باشد:
name
age
gradeسپس سه Student مختلف ایجاد کنید.
تمرین ۲ — Method
به Class قبلی یک Method به نام:
introduce()اضافه کنید که نام و سن دانشآموز را نمایش دهد.
تمرین ۳ — Product
یک Class به نام Product ایجاد کنید که دارای:
name
price
categoryباشد.
سپس Method زیر را ایجاد کنید:
getInfo()این Method باید اطلاعات محصول را برگرداند.
تمرین ۴ — تخفیف
به Class Product یک Method به نام:
applyDiscount(percent)اضافه کنید که قیمت محصول را بر اساس درصد تخفیف کاهش دهد.
تمرین ۵ — وراثت
دو Class ایجاد کنید:
User
AdminAdmin باید از User ارثبری کند و یک ویژگی اضافی به نام:
levelداشته باشد.
تمرین ۶ — Challenge
یک سیستم ساده برای مدیریت حساب بانکی طراحی کنید.
Class شما باید:
BankAccountنام داشته باشد و ویژگیهای زیر را مدیریت کند:
owner
balanceو Methodهای زیر را داشته باشد:
deposit()
withdraw()
getBalance()شرایط:
امکان افزایش موجودی وجود داشته باشد.
برداشت فقط در صورت کافی بودن موجودی انجام شود.
موجودی از طریق یک Method قابل مشاهده باشد.
حداقل دو حساب مختلف ایجاد کنید.
جمعبندی
در این مقاله با یکی از مهمترین مفاهیم جاوااسکریپت یعنی برنامهنویسی شیءگرا یا OOP آشنا شدیم.
یاد گرفتیم:
OOP چیست.
چرا از OOP استفاده میکنیم.
classچیست.constructorچگونه کار میکند.newبرای چه استفاده میشود.Instance چیست.
مفهوم
thisرا بررسی کردیم.Method ایجاد کردیم.
اطلاعات Instance را تغییر دادیم.
از Constructor با مقدار پیشفرض استفاده کردیم.
چند Instance از یک Class ساختیم.
از
instanceofاستفاده کردیم.وراثت با
extendsرا یاد گرفتیم.با
superآشنا شدیم.Method Overriding را بررسی کردیم.
Private Fields با
#را دیدیم.Static Method را معرفی کردیم.
تفاوت Object و Class را بررسی کردیم.
چهار مفهوم اصلی OOP یعنی Encapsulation، Inheritance، Polymorphism و Abstraction را معرفی کردیم.
در ادامه مسیر یادگیری جاوااسکریپت، بهتر است از OOP برای ساختاردهی برنامههای واقعی استفاده کنیم، اما همیشه باید بین سادگی کد و پیچیدگی معماری تعادل برقرار کنیم.
در مقاله بعدی وارد یکی از پرکاربردترین بخشهای جاوااسکریپت یعنی Stringها میشویم و روش ایجاد، دسترسی، جستجو، برش، تغییر و پردازش رشتههای متنی را به صورت عملی یاد خواهیم گرفت.
نظرات
هنوز نظری ثبت نشده است.