آکادمی بنو/آموزش JavaScript

برنامه‌نویسی شیءگرا (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

برابر است با:

25

new چیست؟

برای ساخت یک Object جدید از یک Class معمولاً از new استفاده می‌کنیم.

مثال:

const user1 = new User("Ali", 25);

در اینجا:

User

Class است و:

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
25

Method دارای پارامتر

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
Admin

Admin باید از 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ها می‌شویم و روش ایجاد، دسترسی، جستجو، برش، تغییر و پردازش رشته‌های متنی را به صورت عملی یاد خواهیم گرفت.

۰۰

نظرات

کد در دسترس نیست

هنوز نظری ثبت نشده است.