Inheritance

ما هي الوراثة؟

الوراثة هي مفهوم في البرمجة الكائنية (OOP) يسمح لك بإنشاء كلاس جديد (الابن) يرث الخصائص والدوال من كلاس آخر (الأب).

🧠 فوائد الوراثة:
  1. إعادة استخدام الكود (Reuse).
  2. تقليل التكرار.
  3. تنظيم العلاقات بين الكائنات.
  4. تسهيل التوسعة والتعديل.
📘 الأساسيات في JavaScript:
extends

يُستخدم لجعل كلاس يرث من كلاس آخر.

super()

يُستخدم داخل constructor لاستدعاء دالة البناء (constructor) الخاصة بالكلاس الأب.

🧪 مثال بسيط على الوراثة:
class Employee {
  constructor(name) {
    this.name = name;
  }

  work() {
    return `${this.name} is working.`;
  }
}

class Manager extends Employee {
  constructor(name, department) {
    super(name); // ضروري لاستدعاء constructor الأب
    this.department = department;
  }

  work() {
    return `${this.name} is managing the ${this.department} department.`;
  }
}

let manager1 = new Manager("Ahmed", "IT");
console.log(manager1.work()); // Ahmed is managing the IT department.
JavaScript
شرح سريع:
  • Employee هي الكلاس الأب وتمثل الموظف العادي.
  • Manager هي الكلاس الابن وتمثل المدير الذي يرث من Employee.
  • استخدمنا super(name) لاستدعاء مُنشئ (constructor) الكلاس الأب.
  • قمنا بعمل Override للدالة work داخل Manager لتعبر عن وظيفة المدير بشكل خاص.

الوصول إلى دوال الأب بدون تعديل (بدون Override):

class Employee {
  constructor(name) {
    this.name = name;
  }

  work() {
    return `${this.name} is working.`;
  }
}

class RegularEmployee extends Employee {
  // لا نعيد تعريف work()، سيتم استخدام الدالة من الكلاس الأب
}

let emp1 = new RegularEmployee("Sara");
console.log(emp1.work()); // Sara is working.
JavaScript
شرح:
  • Employee: الكلاس الأساسي يمثل موظفًا عاديًا.
  • RegularEmployee: كلاس يرث من Employee لكن لا يضيف أو يغير أي شيء.
  • الكائن emp1 عند استدعاء work() عليه، سينفذ الدالة الموروثة من Employee.

🔄 تعديل دالة (Override Method)

عند استخدام الوراثة، الكلاس الابن يمكنه إعادة تعريف دوال الكلاس الأب ليغير سلوكها حسب الحاجة، وهذا ما يُسمى Override.

مثال توضيحي:

نأخذ الكلاس Employee ونضيف كلاس Intern (متدرب) يعيد تعريف work() ليعبر عن أن المتدرب يتعلم، ليس فقط يعمل.

class Employee {
  constructor(name) {
    this.name = name;
  }

  work() {
    return `${this.name} is working.`;
  }
}

class Intern extends Employee {
  work() {
    return `${this.name} is learning and assisting.`;
  }
}

let intern1 = new Intern("Yousef");
console.log(intern1.work()); // Yousef is learning and assisting.
JavaScript
ما حدث هنا:
  • الكلاس Intern ورث من Employee.
  • أعدنا تعريف (Override) دالة work() في Intern.
  • لذلك عند استدعاء intern1.work() تم استخدام النسخة الخاصة بـ Intern وليس النسخة الأصلية من Employee.

✅ مثال: الوصول إلى دالة الأب من دالة الإبن في الموظفين:

class Employee {
  constructor(name) {
    this.name = name;
  }

  work() {
    return `${this.name} is working.`;
  }
}

class Manager extends Employee {
  work() {
    return super.work() + ` And also managing the team.`;
  }
}

let m = new Manager("Omar");
console.log(m.work()); // Omar is working. And also managing the team.
JavaScript
🔍 الشرح:
  • Manager يرث من Employee.
  • أعدنا تعريف دالة work() داخل Manager.
  • استخدمنا super.work() للوصول إلى دالة الأب (Employee).
  • أضفنا سلوكًا إضافيًا خاصًا بالمدير بعد نداء دالة الأب.

📌 وراثة الخصائص والدوال:

class Person {
  constructor(name) {
    this.name = name;
  }

  sayHi() {
    return `Hi, I'm ${this.name}`;
  }
}

class Student extends Person {
  study() {
    return `${this.name} is studying.`;
  }
}

let student = new Student("Ahmed");
console.log(student.sayHi()); // Hi, I'm Ahmed
console.log(student.study()); // Ahmed is studying.
JavaScript

في بعض الأمثلة وجود super() داخل الكلاس الابن، وأحيانًا لا تُكتب — والسبب يعود إلى وجود أو عدم وجود constructor داخل الكلاس الابن.

القاعدة الذهبية:

إذا كان الكلاس الابن يحتوي على constructor، فيجب استدعاء super() داخل هذا الـ constructor قبل استخدام this.

📌 الحالة 1: كلاس الابن لا يحتوي على constructor

في هذه الحالة، JavaScript تضيف constructor ضمنيًا وتستدعي super() تلقائيًا، لذلك لا تحتاج لكتابته يدويًا.

✅ مثال:
class Animal {
  constructor(name) {
    this.name = name;
  }
}

class Cat extends Animal {
  // لا يوجد constructor هنا
}

let c = new Cat("Kitty");
console.log(c.name); // Kitty
JavaScript

✅ هنا Cat يرث من Animal، وJS تنشئ constructor تلقائيًا كالتالي:

constructor(...args) {
  super(...args);
}
JavaScript
📌 الحالة 2: كلاس الابن يحتوي على constructor خاص به

هنا مطلوب كتابة super() يدويًا لأنه لا يتم إضافته تلقائيًا، ويجب أن تكتبه قبل استخدام this.

❌ بدون super:
class Cat extends Animal {
  constructor(name) {
    this.name = name; // ❌ خطأ: يجب استدعاء super أولاً
  }
}
JavaScript

✅ مع super:

class Cat extends Animal {
  constructor(name) {
    super(name); // ✅ استدعاء constructor الأب
    this.type = "Cute Cat";
  }
}
JavaScript

🟢 إذا لم تكن بحاجة لتعريف constructor خاص في الكلاس الابن — يمكنك ببساطة عدم كتابته إطلاقًا، وستقوم JavaScript تلقائيًا بإنشاء واحد داخليًا يقوم بما يلي:

🧠 لماذا يجب استخدام super()؟

لأن JavaScript تحتاج إلى تهيئة الجزء الوراثي (الأب) أولًا قبل أن تسمح باستخدام this في الكلاس الابن.

🎯 متى لا تحتاج إلى كتابة constructor؟
  • إذا لم تكن تضيف خصائص جديدة داخل الكلاس الابن.
  • أو لا تقوم بأي تهيئة إضافية غير التي يرثها من الأب.

مثال توضيحي بدون constructor:

class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    return `${this.name} makes a sound`;
  }
}

class Dog extends Animal {
  // لا يوجد constructor هنا
}

let dog1 = new Dog("Rex");
console.log(dog1.speak()); // Rex makes a sound
JavaScript

✔️ كل شيء يعمل، لأن JavaScript أضافت constructor ضمنيًا فيه super(name).

🟡 لكن… إذا أردت تخصيص خصائص في الابن، تحتاج إلى كتابة constructor:

class Dog extends Animal {
  constructor(name, breed) {
    super(name);           // ✅ لازم نبدأ بـ super()
    this.breed = breed;    // خاصية جديدة للابن
  }
}
JavaScript
✳️ خلاصة سريعة:
الحالةتكتب constructor؟تكتب super؟
فقط تورث خصائص من الأب❌ لا❌ لا
تريد إضافة خصائص جديدة في الإبن✅ نعم✅ نعم
تعدل على constructor الأب✅ نعم✅ نعم

📝 خلاصة:

الحالةهل يجب كتابة super()؟
لا يوجد constructor في الإبن❌ لا، JavaScript تفعل ذلك تلقائيًا
يوجد constructor في الإبن✅ نعم، وإلا سيحدث خطأ
❗ ملاحظات مهمة:
المعلومةالتوضيح
يجب استخدام super() داخل constructor الابن قبل استخدام this✅ إجباري
يمكن للكلاس الابن استخدام كل خصائص ودوال الأب
يمكن تعديل (Override) أي دالة في الأب
يمكن وراثة الوراثة (سلسلة inheritance)

🔗 وراثة متعددة المستويات (Multi-level Inheritance):

class A {
  hello() {
    return "Hello from A";
  }
}

class B extends A {
  hi() {
    return "Hi from B";
  }
}

class C extends B {
  greet() {
    return "Greetings from C";
  }
}

let obj = new C();
console.log(obj.hello()); // Hello from A
console.log(obj.hi());    // Hi from B
console.log(obj.greet()); // Greetings from C
JavaScript

🚫 الوراثة المتعددة (Multiple Inheritance) غير مدعومة بشكل مباشر في JavaScript

لا يمكن أن يرث كلاس من أكثر من كلاس مباشرة:

خلاصة الوراثة في JavaScript
المفهومالوظيفة
extendsلربط كلاس بابن يرث من كلاس آخر
super()لاستدعاء constructor أو دوال الأب
override methodتعديل دالة موروثة داخل الإبن
super.method()استدعاء دالة من الأب داخل الإبن
الوراثة المتعددةغير مدعومة مباشرة، يمكن استخدام mixins
ما هي super؟

super هي كلمة مفتاحية (keyword) تُستخدم داخل الكلاس الابن للإشارة إلى الكلاس الأب.

🔧 استخدامات super في JavaScript:
الاستخدامالهدف
super(args)لاستدعاء constructor الخاص بالكلاس الأب
super.methodName()لاستدعاء دالة من الأب داخل كلاس الابن (حتى لو تم عمل override لها)
📘 1. super() داخل constructor:

تُستخدم لاستدعاء constructor الكلاس الأب من داخل constructor الكلاس الابن.

✅ مثال:
class Parent {
  constructor(name) {
    this.name = name;
  }
}

class Child extends Parent {
  constructor(name, age) {
    super(name);      // 👈 ضروري: يستدعي constructor الأب
    this.age = age;   // 👈 خاص بالكلاس الابن
  }
}

let c = new Child("Ali", 20);
console.log(c.name); // Ali
console.log(c.age);  // 20
JavaScript

إذا لم تكتب super() قبل استخدام this في constructor الابن، ستحصل على خطأ.

📗 2. super.method() لاستدعاء دوال الأب:

يمكنك استخدام super.method() داخل أي دالة في كلاس الابن، حتى لو كنت عملت Override للدالة.

✅ مثال:
class Parent {
  greet() {
    return "Hello from Parent";
  }
}

class Child extends Parent {
  greet() {
    let parentMessage = super.greet(); // 👈 استدعاء دالة الأب
    return parentMessage + " and Hello from Child";
  }
}

let obj = new Child();
console.log(obj.greet()); 
// Hello from Parent and Hello from Child
JavaScript
📙 استخدام super في الدوال العادية مقابل constructor
السياقالشكلالوظيفة
داخل constructorsuper(args)استدعاء constructor الأب
داخل دالةsuper.method()استدعاء دالة من كلاس الأب
🛑 ملاحظات مهمة:
  • يجب استدعاء super() قبل استخدام this داخل constructor.
  • لا يمكن استخدام super إلا داخل كلاس يرث من آخر (class Child extends Parent).
  • لا يمكنك استخدام super في كلاس لا يرث (extends) من كلاس آخر.
🔥 مثال شامل يوضح الحالتين:
class Vehicle {
  constructor(type) {
    this.type = type;
  }

  move() {
    return `${this.type} is moving`;
  }
}

class Car extends Vehicle {
  constructor(type, brand) {
    super(type); // ← يستدعي constructor الأب
    this.brand = brand;
  }

  move() {
    return super.move() + ` - Brand: ${this.brand}`;
  }
}

let c = new Car("Car", "Toyota");
console.log(c.move()); 
// Car is moving - Brand: Toyota
JavaScript
✅ خلاصة:
ما تعني super؟متى تستخدم؟
استدعاء constructor الأبداخل constructor الابن مع super(args)
استدعاء دالة من الأبداخل دالة في الابن باستخدام super.method()