Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

الـ Method Overriding (في وقت التشغيل)

جزء من قسم Object Oriented Programming في رحلة Java على Coddy — الدرس 29 من 87.

بينما يتم حل تحميل الدوال الزائد (method overloading) في وقت التجميع (compile time)، فإن إعادة تعريف الدوال (method overriding) تُظهر تعدد الأشكال في وقت التشغيل (run-time polymorphism). هنا، تقرر Java أي دالة سيتم تنفيذها عندما يكون البرنامج قيد التشغيل بالفعل، بناءً على النوع الفعلي للكائن (object) بدلاً من النوع المعلن للمتغير.

لقد تعلمت عن تجاوز الدوال (method overriding) في فصل الوراثة (Inheritance). ما يجعله متعدد الأشكال (polymorphic) هو كيفية تعامل Java معه عندما يشير مرجع من الفئة الأب (parent reference) إلى كائن من الفئة الابن (child object):

public class Animal {
    public void speak() {
        System.out.println("Some sound");
    }
}

public class Dog extends Animal {
    @Override
    public void speak() {
        System.out.println("Bark!");
    }
}

public class Cat extends Animal {
    @Override
    public void speak() {
        System.out.println("Meow!");
    }
}

الآن لاحظ ما يحدث عندما نستخدم مراجع من نوع الفئة الأساسية (parent-type):

Animal myPet = new Dog();
myPet.speak();  // المخرجات: Bark!

myPet = new Cat();
myPet.speak();  // المخرجات: Meow!

على الرغم من أن myPet تم التصريح عنه كـ Animal، إلا أن Java تستدعي الطريقة التي تم تجاوزها (overridden method) بناءً على نوع الكائن الفعلي في وقت التشغيل (runtime). يُسمى هذا الإرسال الديناميكي للطرق (dynamic method dispatch). ينظر الـ JVM إلى ماهية الكائن الحقيقية، وليس إلى ما يقوله المتغير عنه.

هذا يتيح أنماطاً قوية مثل معالجة كائنات مختلفة بشكل موحد:

Animal[] pets = {new Dog(), new Cat(), new Dog()};
for (Animal pet : pets) {
    pet.speak();  // كل واحد يستدعي نسخته الخاصة
}

يتيح لك تعدد الأشكال في وقت التشغيل (Run-time polymorphism) كتابة كود مرن يعمل مع الأنواع الأب (parent types) مع استخدام سلوك الابن (child behavior) الصحيح تلقائياً.

challenge icon

التحدي

سهل

لنقم ببناء نظام إشعارات يوضح تعدد الأشكال في وقت التشغيل (run-time polymorphism) بشكل عملي. ستقوم بإنشاء تسلسل هرمي لأنواع الإشعارات حيث ينتج عن استدعاء نفس الطريقة مخرجات مختلفة اعتمادًا على نوع الكائن الفعلي في وقت التشغيل.

ستقوم بتنظيم الكود الخاص بك عبر أربعة ملفات:

  • Notification.java: قم بإنشاء الفئة الأساسية (base class) التي تشترك فيها جميع الإشعارات. يحتوي كل إشعار على حقل recipient (String). قم بتضمين منشئ (constructor) لتهيئته، وطريقة getter، وطريقة send() التي تطبع: Sending notification to [recipient]
  • EmailNotification.java: قم بإنشاء فئة ترث من Notification. تضيف إشعارات البريد الإلكتروني حقل subject. استخدم super لتهيئة الفئة الأب. قم بإعادة تعريف (Override) طريقة send() لتطبع: Emailing [recipient]: [subject]
  • SMSNotification.java: قم بإنشاء فئة أخرى ترث من Notification. تضيف إشعارات SMS حقل phoneNumber. قم بإعادة تعريف send() لتطبع: Texting [phoneNumber] for [recipient]
  • Main.java: هنا يبرز تعدد الأشكال في وقت التشغيل! ستتلقى ثلاثة مدخلات: اسم المستلم، وموضوع البريد الإلكتروني، ورقم الهاتف. قم بإنشاء مصفوفة من مراجع Notification تحتوي على ثلاثة كائنات بهذا الترتيب: Notification أساسي، و EmailNotification، و SMSNotification (جميعها تستخدم نفس المستلم). ثم قم بالمرور عبر المصفوفة واستدعاء send() على كل عنصر. شاهد كيف يقوم Java تلقائيًا باستدعاء النسخة المعاد تعريفها الصحيحة بناءً على ماهية كل كائن في الواقع!

ستتلقى ثلاثة مدخلات: اسم المستلم (String)، وموضوع البريد الإلكتروني (String)، ورقم الهاتف (String).

يجب أن يظهر مخرجك ثلاثة أسطر - كل استدعاء لـ send() ينتج مخرجًا مختلفًا على الرغم من أنك تستدعي نفس الطريقة على مراجع Notification. هذا هو الإرسال الديناميكي للطرق (dynamic method dispatch) أثناء العمل!

ورقة مرجعية

يوضح تجاوز الدوال (Method overriding) **تعدد الأشكال في وقت التشغيل (run-time polymorphism)**، حيث تقرر لغة Java أي دالة سيتم تنفيذها أثناء تشغيل البرنامج، بناءً على النوع الفعلي للكائن بدلاً من النوع المعلن للمتغير.

عندما يشير مرجع من الفئة الأب إلى كائن من الفئة الابن، تستدعي Java الدالة التي تم تجاوزها بناءً على نوع الكائن الفعلي في وقت التشغيل:

public class Animal {
    public void speak() {
        System.out.println("Some sound");
    }
}

public class Dog extends Animal {
    @Override
    public void speak() {
        System.out.println("Bark!");
    }
}

Animal myPet = new Dog();
myPet.speak();  // Output: Bark!

myPet = new Cat();
myPet.speak();  // Output: Meow!

يسمى هذا **الإرسال الديناميكي للدوال (dynamic method dispatch)**. ينظر الـ JVM إلى ماهية الكائن الحقيقية، وليس إلى ما يقوله المتغير.

يتيح تعدد الأشكال في وقت التشغيل معالجة كائنات مختلفة بشكل موحد:

Animal[] pets = {new Dog(), new Cat(), new Dog()};
for (Animal pet : pets) {
    pet.speak();  // كل منها يستدعي نسخته الخاصة
}

جرّب بنفسك

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // قراءة المدخلات
        String recipient = scanner.nextLine();
        String subject = scanner.nextLine();
        String phoneNumber = scanner.nextLine();
        
        // TODO: إنشاء مصفوفة من مراجع Notification تحتوي على 3 عناصر
        // يجب أن تحتوي المصفوفة على (بهذا الترتيب):
        // 1. كائن Notification أساسي
        // 2. كائن EmailNotification
        // 3. كائن SMSNotification
        // جميعها تستخدم نفس الـ recipient
        
        // TODO: قم بالمرور عبر المصفوفة واستدعاء ()send لكل عنصر
        // هذا يوضح تعدد الأشكال في وقت التشغيل (run-time polymorphism)!
    }
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming