تعدد الأشكال في لغة JavaScript


تعدد الأشكال في لغة JavaScript

يا هلا بالشباب! اليوم بنتكلم عن مفهوم حلو ومهم في البرمجة الكائنية (OOP) اسمه تعدد الأشكال (Polymorphism)، وكيف يشتغل في JavaScript. الموضوع بسيط ومو معقد زي ما تتخيل.

إيش هو تعدد الأشكال أصلاً؟

ببساطة، تعدد الأشكال يعني "أشكال متعددة". في البرمجة، يعني إنك تقدر تتعامل مع كائنات مختلفة بنفس الطريقة، حتى لو كانت أنواعها مختلفة، بس بشرط إنها تشترك في سلوك معين (يعني عندها نفس الميثودات). الهدف؟ كود مرن، سهل التوسيع، وأقل تكرار.

1. تجاوز الميثودات (Method Overriding)

هذا أشهر أنواع تعدد الأشكال في JS. لما يكون عندك كلاس أب (Parent Class) وكلاس ابن (Child Class)، والابن يبي يسوي نفس الميثود اللي عند الأب بس بطريقته الخاصة، هنا يجي دور الـ overriding. شوف المثال:

ملاحظة: في JavaScript، بنستخدم الكلاسات الحديثة (ES6 Classes) عشان نوضح الفكرة، رغم إن JS مبنية على الـ Prototypal Inheritance.


class Animal {
    makeSound() {
        return "صوت حيوان!";
    }
}

class Dog extends Animal {
    makeSound() {
        return "هاو هاو!";
    }
}

class Cat extends Animal {
    makeSound() {
        return "مياو مياو!";
    }
}

const genericAnimal = new Animal();
const myDog = new Dog();
const myCat = new Cat();

console.log(genericAnimal.makeSound()); // صوت حيوان!
console.log(myDog.makeSound());        // هاو هاو!
console.log(myCat.makeSound());        // مياو مياو!

function animalSays(animal) {
    console.log(animal.makeSound());
}

animalSays(myDog); // هاو هاو!
animalSays(myCat); // مياو مياو!
animalSays(genericAnimal); // صوت حيوان!

شفنا كيف دالة animalSays قدرت تتعامل مع أي كائن عنده ميثود makeSound()، بغض النظر عن نوعه الحقيقي (كلب، قطة، حيوان عام). هذا هو تعدد الأشكال في أبسط صوره.

2. تعدد الأشكال عن طريق "Duck Typing" (بطريقة البطة)

هذا هو قلب تعدد الأشكال في JavaScript! JS ما عندها مفهوم الـ Interfaces أو الـ Abstract Classes زي لغات ثانية. بدالها، تعتمد على مبدأ "إذا كان يمشي زي البطة ويصيح زي البطة، فهو بطة." يعني، إذا كان الكائن عنده الميثودات اللي نحتاجها، نقدر نتعامل معاه وكأنه من النوع اللي نبيه، بغض النظر عن الكلاس اللي جاي منه.


class Car {
    start() {
        return "السيارة بدأت!";
    }
    drive() {
        return "السيارة تسير.";
    }
}

class Bicycle {
    start() {
        return "الدراجة جاهزة للانطلاق!";
    }
    ride() {
        return "الدراجة تسير.";
    }
}

const myCar = new Car();
const myBike = new Bicycle();

function startVehicle(vehicle) {
    // هنا نعتمد على أن الكائن عنده ميثود 'start'
    if (typeof vehicle.start === 'function') {
        console.log(vehicle.start());
    } else {
        console.log("هذا الكائن ما يقدر يبدأ!");
    }
}

startVehicle(myCar);  // السيارة بدأت!
startVehicle(myBike); // الدراجة جاهزة للانطلاق!

// مثال آخر لكائن عادي مو كلاس
const drone = {
    start: () => "الدرون أقلعت!",
    fly: () => "الدرون تحلق في الجو."
};

startVehicle(drone); // الدرون أقلعت!

// كائن ما عنده ميثود 'start'
const rock = {};
startVehicle(rock); // هذا الكائن ما يقدر يبدأ!

في هذا المثال، دالة startVehicle ما تدري ولا تهتم إذا الكائن اللي جاها Car ولا Bicycle ولا حتى كائن عادي drone. كل اللي يهمها إن الكائن عنده ميثود اسمها start(). هذا هو الـ Duck Typing، وهو اللي يخلي JS مرنة جداً.

3. محاكاة تجاوز الدوال (Method Overloading Simulation)

لغات زي Java و C++ عندها Method Overloading، يعني تقدر تسوي عدة دوال بنفس الاسم بس تختلف في عدد أو أنواع الباراميترات. JavaScript ما تدعم هذا الشيء بشكل مباشر. لكن نقدر نسويه بطرق مختلفة:

  • التعامل مع عدد الباراميترات: استخدام arguments object أو فحص length الباراميترات.
  • التعامل مع نوع الباراميترات: فحص نوع المتغيرات باستخدام typeof.

function displayInfo(value1, value2) {
    if (value2 === undefined) {
        // حالة باراميتر واحد
        console.log(تم إدخال قيمة واحدة: ${value1});
    } else if (typeof value1 === 'string' && typeof value2 === 'string') {
        // حالة بارامترين من نوع string
        console.log(اسم كامل: ${value1} ${value2});
    } else if (typeof value1 === 'number' && typeof value2 === 'number') {
        // حالة بارامترين من نوع number
        console.log(مجموع الأرقام: ${value1 + value2});
    } else {
        console.log("نوع غير مدعوم!");
    }
}

displayInfo("أحمد");       // تم إدخال قيمة واحدة: أحمد
displayInfo("محمد", "علي"); // اسم كامل: محمد علي
displayInfo(10, 20);       // مجموع الأرقام: 30
displayInfo(true, false);  // نوع غير مدعوم!

هذا ليس "overloading" حقيقي، بل هو طريقة للتعامل مع السيناريوهات المختلفة داخل نفس الدالة. الـ overloading الحقيقي يكون عندك عدة تعريفات للدالة، والمترجم/المفسر يختار الأنسب بناءً على الباراميترات.

ليش تعدد الأشكال مهم؟

  • مرونة الكود: تقدر تكتب كود يتعامل مع أنواع مختلفة من الكائنات بنفس الطريقة، وهذا يقلل من الحاجة لكتابة شروط if/else معقدة.
  • سهولة التوسيع: لما تضيف أنواع كائنات جديدة، ما تحتاج تعدل الكود القديم اللي يتعامل معاها، طالما إن الكائنات الجديدة تطبق نفس الميثودات المطلوبة.
  • تنظيم الكود: يخلي الكود أنظف وأسهل للقراءة والصيانة.

وبكذا نكون غطينا أهم جوانب تعدد الأشكال في JavaScript. الموضوع بسيط ويعطيك قوة كبيرة في بناء أنظمة مرنة وقابلة للتوسع. طبق الأمثلة بنفسك عشان ترسخ الفكرة أكثر!