JavaScript المتقدم (ES6+)

وراثة الفئات

13 دقيقة الدرس 27 من 40

وراثة الفئات

الوراثة هي مفهوم أساسي في البرمجة الكائنية التي تسمح لك بإنشاء فئات جديدة بناءً على فئات موجودة. تجعل ES6 الوراثة في JavaScript أنظف وأكثر سهولة باستخدام الكلمات المفتاحية extends و super.

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

تسمح الوراثة لفئة (فئة فرعية/ابن) بوراثة الخصائص والدوال من فئة أخرى (فئة أساسية/أب). هذا يعزز إعادة استخدام الكود ويُنشئ علاقات بين الفئات.

مفهوم أساسي: تُنشئ الوراثة علاقة "هو من نوع". على سبيل المثال، الكلب هو حيوان، السيارة هي مركبة، والمدير هو موظف.

الكلمة المفتاحية extends

تُستخدم الكلمة المفتاحية extends لإنشاء فئة ترث من فئة أخرى:

// الفئة الأساسية (الفئة الأم) class Animal { constructor(name, age) { this.name = name; this.age = age; } makeSound() { return "Some generic animal sound"; } info() { return `${this.name} is ${this.age} years old`; } } // الفئة الفرعية (الفئة الابن) class Dog extends Animal { constructor(name, age, breed) { super(name, age); // استدعاء مُنشئ الفئة الأساسية this.breed = breed; } makeSound() { return "Woof! Woof!"; } fetch() { return `${this.name} is fetching the ball!`; } } const myDog = new Dog("Max", 3, "Golden Retriever"); console.log(myDog.info()); // "Max is 3 years old" (موروثة) console.log(myDog.makeSound()); // "Woof! Woof!" (تم تجاوزها) console.log(myDog.fetch()); // "Max is fetching the ball!" (دالة جديدة) console.log(myDog.breed); // "Golden Retriever"

الكلمة المفتاحية super

تُستخدم الكلمة المفتاحية super لاستدعاء دوال من الفئة الأساسية:

class Vehicle { constructor(brand, model) { this.brand = brand; this.model = model; this.speed = 0; } accelerate(amount) { this.speed += amount; return `Speed: ${this.speed} mph`; } describe() { return `${this.brand} ${this.model}`; } } class Car extends Vehicle { constructor(brand, model, doors) { super(brand, model); // استدعاء مُنشئ الفئة الأساسية this.doors = doors; } // تجاوز دالة الفئة الأساسية accelerate(amount) { // استدعاء دالة الفئة الأساسية super.accelerate(amount); return `${this.describe()} accelerating... ${this.speed} mph`; } // إضافة دالة جديدة honk() { return "Beep beep!"; } } const myCar = new Car("Toyota", "Camry", 4); console.log(myCar.accelerate(30)); // "Toyota Camry accelerating... 30 mph" console.log(myCar.accelerate(20)); // "Toyota Camry accelerating... 50 mph" console.log(myCar.honk()); // "Beep beep!"
مهم: إذا عرّفت مُنشئاً في فئة فرعية، يجب عليك استدعاء super() قبل الوصول إلى this. وإلا، ستحصل على ReferenceError.

تجاوز الدوال

يمكن للفئات الفرعية تجاوز دوال الفئة الأساسية لتوفير سلوك متخصص:

class Shape { constructor(color) { this.color = color; } area() { return 0; } describe() { return `A ${this.color} shape with area ${this.area()}`; } } class Rectangle extends Shape { constructor(color, width, height) { super(color); this.width = width; this.height = height; } // تجاوز دالة area area() { return this.width * this.height; } } class Circle extends Shape { constructor(color, radius) { super(color); this.radius = radius; } // تجاوز دالة area area() { return Math.PI * this.radius * this.radius; } } const rect = new Rectangle("blue", 10, 5); const circle = new Circle("red", 7); console.log(rect.describe()); // "A blue shape with area 50" console.log(circle.describe()); // "A red shape with area 153.938..."

سلاسل الوراثة

يمكن للفئات تشكيل سلاسل وراثة حيث يمكن لفئة فرعية أن تكون بدورها أساسية لفئة أخرى:

class LivingBeing { constructor(name) { this.name = name; this.isAlive = true; } breathe() { return `${this.name} is breathing`; } } class Animal extends LivingBeing { constructor(name, species) { super(name); this.species = species; } move() { return `${this.name} is moving`; } } class Mammal extends Animal { constructor(name, species, furColor) { super(name, species); this.furColor = furColor; } nurse() { return `${this.name} is nursing its young`; } } class Dog extends Mammal { constructor(name, breed, furColor) { super(name, "Canine", furColor); this.breed = breed; } bark() { return `${this.name} says: Woof!`; } } const myDog = new Dog("Buddy", "Labrador", "Golden"); console.log(myDog.breathe()); // من LivingBeing console.log(myDog.move()); // من Animal console.log(myDog.nurse()); // من Mammal console.log(myDog.bark()); // من Dog console.log(myDog.species); // "Canine"
تحذير: سلاسل الوراثة العميقة يمكن أن تجعل الكود أصعب في الفهم والصيانة. بشكل عام، تجنب الذهاب أكثر من 2-3 مستويات عمقاً. فكر في التركيب كبديل.

معامل instanceof

استخدم instanceof للتحقق مما إذا كان الكائن نسخة من فئة (أو فئاتها الأساسية):

class Animal { constructor(name) { this.name = name; } } class Dog extends Animal { bark() { return "Woof!"; } } class Cat extends Animal { meow() { return "Meow!"; } } const dog = new Dog("Max"); const cat = new Cat("Whiskers"); console.log(dog instanceof Dog); // true console.log(dog instanceof Animal); // true console.log(dog instanceof Cat); // false console.log(cat instanceof Cat); // true console.log(cat instanceof Animal); // true console.log(cat instanceof Dog); // false // مفيد للتحقق من الأنواع function makeAnimalSound(animal) { if (animal instanceof Dog) { return animal.bark(); } else if (animal instanceof Cat) { return animal.meow(); } return "Unknown animal"; } console.log(makeAnimalSound(dog)); // "Woof!" console.log(makeAnimalSound(cat)); // "Meow!"

حماية الدوال باستخدام super

يمكنك استخدام super لتوسيع وظائف الفئة الأساسية بدلاً من استبدالها بالكامل:

class BankAccount { constructor(accountNumber, balance) { this.accountNumber = accountNumber; this.balance = balance; this.transactions = []; } deposit(amount) { this.balance += amount; this.transactions.push({ type: "deposit", amount, date: new Date() }); return `Deposited $${amount}. New balance: $${this.balance}`; } withdraw(amount) { if (amount > this.balance) { return "Insufficient funds"; } this.balance -= amount; this.transactions.push({ type: "withdrawal", amount, date: new Date() }); return `Withdrew $${amount}. New balance: $${this.balance}`; } } class SavingsAccount extends BankAccount { constructor(accountNumber, balance, interestRate) { super(accountNumber, balance); this.interestRate = interestRate; this.withdrawalLimit = 6; // لائحة فيدرالية this.withdrawalCount = 0; } withdraw(amount) { if (this.withdrawalCount >= this.withdrawalLimit) { return "Monthly withdrawal limit reached"; } // استدعاء دالة withdraw من الفئة الأساسية const result = super.withdraw(amount); if (result.includes("Withdrew")) { this.withdrawalCount++; } return result; } addInterest() { const interest = this.balance * (this.interestRate / 100); this.deposit(interest); return `Interest added: $${interest.toFixed(2)}`; } resetWithdrawals() { this.withdrawalCount = 0; return "Monthly withdrawal count reset"; } } const savings = new SavingsAccount("SAV-001", 1000, 2.5); console.log(savings.deposit(500)); // "Deposited $500. New balance: $1500" console.log(savings.withdraw(100)); // "Withdrew $100. New balance: $1400" console.log(savings.addInterest()); // "Interest added: $35.00"

التركيب مقابل الوراثة

بينما الوراثة قوية، فإن التركيب (دمج الكائنات) غالباً ما يكون خياراً أفضل:

// نهج الوراثة (يمكن أن يصبح معقداً) class FlyingAnimal extends Animal { fly() { return "Flying"; } } class SwimmingAnimal extends Animal { swim() { return "Swimming"; } } // ماذا عن الحيوانات التي تطير وتسبح؟ // نهج التركيب (أكثر مرونة) class Animal { constructor(name, abilities = []) { this.name = name; this.abilities = abilities; } can(ability) { return this.abilities.includes(ability); } perform(action) { if (this.can(action)) { return `${this.name} is ${action}`; } return `${this.name} cannot ${action}`; } } // إنشاء كائنات القدرات const flying = { fly() { return "flying high"; } }; const swimming = { swim() { return "swimming gracefully"; } }; const walking = { walk() { return "walking on land"; } }; // تركيب القدرات const duck = new Animal("Duck", ["flying", "swimming", "walking"]); const fish = new Animal("Fish", ["swimming"]); const bird = new Animal("Eagle", ["flying", "walking"]); console.log(duck.perform("flying")); // "Duck is flying" console.log(duck.perform("swimming")); // "Duck is swimming" console.log(fish.perform("flying")); // "Fish cannot flying"
أفضل ممارسة: فضّل التركيب على الوراثة عندما يكون ذلك ممكناً. استخدم الوراثة للعلاقات الحقيقية "هو من نوع" والتركيب للعلاقات "لديه" أو "يستطيع عمل".

الدوال الثابتة والوراثة

الدوال الثابتة تُورث أيضاً بواسطة الفئات الفرعية:

class MathOperations { static add(a, b) { return a + b; } static multiply(a, b) { return a * b; } } class AdvancedMath extends MathOperations { static power(base, exponent) { return Math.pow(base, exponent); } static squareRoot(num) { return Math.sqrt(num); } } // الفئة الفرعية لديها وصول إلى الدوال الثابتة للفئة الأساسية console.log(AdvancedMath.add(5, 3)); // 8 (موروثة) console.log(AdvancedMath.multiply(4, 6)); // 24 (موروثة) console.log(AdvancedMath.power(2, 3)); // 8 (دالتها الخاصة) console.log(AdvancedMath.squareRoot(16)); // 4 (دالتها الخاصة)

تمرين تطبيقي:

التحدي: أنشئ نظام إدارة موظفين بالمتطلبات التالية:

  • فئة Employee: name, id, salary، ودالة calculateBonus() (10% من الراتب)
  • Manager يمتد من Employee: إضافة teamSize، تجاوز calculateBonus() (15% + 100$ لكل عضو فريق)
  • Developer يمتد من Employee: إضافة مصفوفة programmingLanguages، إضافة دالة لإضافة لغات
  • Designer يمتد من Employee: إضافة مصفوفة designTools، تجاوز calculateBonus() (12% من الراتب)

الحل:

class Employee { constructor(name, id, salary) { this.name = name; this.id = id; this.salary = salary; } calculateBonus() { return this.salary * 0.10; } getInfo() { return `${this.name} (ID: ${this.id}) - Salary: $${this.salary}`; } } class Manager extends Employee { constructor(name, id, salary, teamSize) { super(name, id, salary); this.teamSize = teamSize; } calculateBonus() { return (this.salary * 0.15) + (this.teamSize * 100); } } class Developer extends Employee { constructor(name, id, salary, programmingLanguages = []) { super(name, id, salary); this.programmingLanguages = programmingLanguages; } addLanguage(language) { if (!this.programmingLanguages.includes(language)) { this.programmingLanguages.push(language); } return `${language} added. Known languages: ${this.programmingLanguages.join(", ")}`; } } class Designer extends Employee { constructor(name, id, salary, designTools = []) { super(name, id, salary); this.designTools = designTools; } calculateBonus() { return this.salary * 0.12; } } // اختبار النظام const manager = new Manager("Alice", "M001", 80000, 5); console.log(manager.getInfo()); // "Alice (ID: M001) - Salary: $80000" console.log("Bonus: $" + manager.calculateBonus()); // Bonus: $12500 const dev = new Developer("Bob", "D001", 70000, ["JavaScript"]); console.log(dev.addLanguage("Python")); // "Python added. Known languages: JavaScript, Python" console.log("Bonus: $" + dev.calculateBonus()); // Bonus: $7000 const designer = new Designer("Carol", "DS001", 65000, ["Figma", "Photoshop"]); console.log("Bonus: $" + designer.calculateBonus()); // Bonus: $7800

الملخص

في هذا الدرس، تعلمت:

  • الكلمة المفتاحية extends تُنشئ علاقات وراثة
  • الكلمة المفتاحية super تستدعي دوال ومُنشئات الفئة الأساسية
  • الفئات الفرعية يمكنها تجاوز دوال الفئة الأساسية للسلوك المتخصص
  • سلاسل الوراثة تسمح بتسلسلات هرمية متعددة المستويات
  • معامل instanceof يتحقق من عضوية الفئة
  • التركيب غالباً ما يكون أفضل من الوراثة العميقة
  • الدوال الثابتة تُورث بواسطة الفئات الفرعية
التالي: في الدرس التالي، سنتعمق في النماذج الأولية وسلسلة النموذج الأولي لفهم ما يحدث خلف الكواليس مع الفئات والوراثة!