البرمجة الكائنية في لغة JavaScript
يا هلا والله! اليوم بنتكلم عن موضوع أساسي ومهم جداً في عالم البرمجة: البرمجة الكائنية (Object-Oriented Programming - OOP)، وكيف نطبقها في JavaScript. لا تشيل هم، الموضوع أبسط مما تتخيل لو ركزت شوي.
وش سالفة OOP؟
الـ OOP هي طريقة لتنظيم الكود حقك بحيث تحاكي العالم الحقيقي. بدل ما تكتب دوال كذا لحالها، تجمع البيانات (الخصائص) والدوال اللي تشتغل عليها (السلوكيات) في "كائنات" (Objects). تخيل سيارة، لها لون (خاصية)، موديل (خاصية)، وتقدر تمشي (سلوك)، وتوقف (سلوك). هذا هو الكائن باختصار.
ملاحظة سريعة: JavaScript لغة "كائنية" بطبيعتها حتى قبل ظهور الـ class في ES6. كل شيء فيها تقريباً كائن، أو يتعامل ككائن. لكن مع ES6، صار عندنا طريقة أسهل وأوضح لكتابة كود OOP.
الكائنات (Objects)
في JavaScript، الكائن هو مجموعة من الخصائص والدوال. تقدر تسويه بكذا طريقة، أبسطها هي الـ Object Literal:
const car = {
brand: 'Toyota',
model: 'Camry',
year: 2023,
start: function() {
console.log('Engine started for ' + this.brand + ' ' + this.model);
},
stop() { // اختصار لكتابة الدالة
console.log('Engine stopped.');
}
};
console.log(car.brand); // Toyota
car.start(); // Engine started for Toyota Camry
الفئات (Classes)
الفئات هي زي "القوالب" أو "المخططات" اللي نسوي منها الكائنات. تخيل عندك مخطط لسيارة، تقدر تصنع منه مليون سيارة وكلها بنفس المواصفات الأساسية. في ES6، نستخدم الكلمة المفتاحية class:
class Car {
constructor(brand, model, year) {
this.brand = brand;
this.model = model;
this.year = year;
}
start() {
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">Engine started for ${this.brand} ${this.model}</code>);
}
stop() {
console.log('Engine stopped.');
}
}
const myCar = new Car('Hyundai', 'Elantra', 2020);
const yourCar = new Car('Mercedes', 'C-Class', 2024);
console.log(myCar.model); // Elantra
myCar.start(); // Engine started for Hyundai Elantra
yourCar.stop(); // Engine stopped.
نقطة مهمة: الدالة constructor هي دالة خاصة يتم استدعائها تلقائياً لما تنشئ كائن جديد من الفئة (باستخدام new). وظيفتها الأساسية هي تهيئة خصائص الكائن.
التغليف (Encapsulation)
التغليف يعني إنك تخفي التفاصيل الداخلية للكائن وتوفر واجهة واضحة للتعامل معاه. يعني ما تخلي أي أحد يغير خصائص مهمة مباشرة، بدال كذا توفر دوال (methods) خاصة للتعديل أو الوصول. في JavaScript، نقدر نسوي حقول خاصة (private fields) باستخدام علامة الهاش #:
class BankAccount {
#balance; // حقل خاص
constructor(initialBalance) {
if (initialBalance < 0) {
throw new Error('Initial balance cannot be negative.');
}
this.#balance = initialBalance;
}
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">Deposited ${amount}. New balance: ${this.#balance}</code>);
} else {
console.log('Deposit amount must be positive.');
}
}
withdraw(amount) {
if (amount > 0 && amount <= this.#balance) {
this.#balance -= amount;
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">Withdrew ${amount}. New balance: ${this.#balance}</code>);
} else {
console.log('Invalid withdrawal amount or insufficient balance.');
}
}
getBalance() {
return this.#balance;
}
}
const myAccount = new BankAccount(100);
// console.log(myAccount.#balance); // هذا بيطلع خطأ! ما تقدر توصل له مباشرة
myAccount.deposit(50); // Deposited 50. New balance: 150
myAccount.withdraw(30); // Withdrew 30. New balance: 120
console.log('Current balance:', myAccount.getBalance()); // Current balance: 120
الوراثة (Inheritance)
الوراثة تخليك تسوي فئة جديدة (فئة فرعية - subclass) تاخذ كل الخصائص والدوال من فئة موجودة (فئة أساسية - superclass) وتقدر تضيف عليها أو تعدل فيها. هذا يوفر عليك تكرار الكود. نستخدم extends و super:
class Vehicle {
constructor(make, model) {
this.make = make;
this.model = model;
}
start() {
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">${this.make} ${this.model} is starting.</code>);
}
stop() {
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">${this.make} ${this.model} is stopping.</code>);
}
}
class Car extends Vehicle {
constructor(make, model, numDoors) {
super(make, model); // استدعاء constructor الفئة الأساسية
this.numDoors = numDoors;
}
honk() {
console.log('Beep beep!');
}
// نقدر نعدل على دالة موجودة في الفئة الأساسية (Method Overriding)
start() {
super.start(); // استدعاء دالة start من الفئة الأساسية
console.log('Car specific startup checks complete.');
}
}
const mySedan = new Car('Honda', 'Civic', 4);
mySedan.start();
// Honda Civic is starting.
// Car specific startup checks complete.
mySedan.honk(); // Beep beep!
mySedan.stop(); // Honda Civic is stopping.
class ElectricCar extends Car {
constructor(make, model, numDoors, batteryCapacity) {
super(make, model, numDoors);
this.batteryCapacity = batteryCapacity;
}
charge() {
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">${this.make} ${this.model} is charging with ${this.batteryCapacity} kWh battery.</code>);
}
}
const tesla = new ElectricCar('Tesla', 'Model 3', 4, 75);
tesla.start();
tesla.charge(); // Tesla Model 3 is charging with 75 kWh battery.
تذكر: super() في الـ constructor تستدعي constructor الفئة الأب. super.method() تستدعي الدالة اللي بنفس الاسم من الفئة الأب.
تعدد الأشكال (Polymorphism)
تعدد الأشكال يعني إن الكائنات من فئات مختلفة ممكن تستجيب لنفس الرسالة (نفس اسم الدالة) بطرق مختلفة. يعني عندك دالة اسمها drive() مثلاً، ممكن سيارة تسويها بطريقة، ودراجة تسويها بطريقة ثانية، لكن كلهم يستجيبون لـ drive(). أبسط أشكالها هو الـ Method Overriding اللي شفناه في مثال الوراثة، لما عدلنا دالة start() في فئة Car.
مثال آخر يوضح الفكرة:
class Animal {
makeSound() {
console.log('Animal makes a sound');
}
}
class Dog extends Animal {
makeSound() {
console.log('Woof! Woof!');
}
}
class Cat extends Animal {
makeSound() {
console.log('Meow!');
}
}
function animalConcert(animal) {
animal.makeSound();
}
const myDog = new Dog();
const myCat = new Cat();
const genericAnimal = new Animal();
animalConcert(myDog); // Woof! Woof!
animalConcert(myCat); // Meow!
animalConcert(genericAnimal); // Animal makes a sound
هنا دالة animalConcert ما تدري وش نوع الحيوان بالضبط، لكنها تدري إن أي animal عنده دالة makeSound()، وكل حيوان يستجيب لها بطريقته الخاصة. هذا هو تعدد الأشكال.
ختاماً
البرمجة الكائنية في JavaScript أداة قوية جداً لتنظيم الكود وجعله أسهل للقراءة والصيانة والتوسع. لما تستوعب هالمفاهيم الأساسية، بتشوف إنك تقدر تبني تطبيقات أكبر وأكثر تعقيداً بطريقة منظمة وممتازة. طبق الأمثلة وجرب بنفسك عشان تثبت المعلومة صح! بالتوفيق يا بطل!