الـ 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) الصحيح تلقائياً.
التحدي
سهللنقم ببناء نظام إشعارات يوضح تعدد الأشكال في وقت التشغيل (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)!
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات الـ OOP
الملفات الخارجيةمقدمة في الـ OOPالفئات (Classes) مقابل الكائنات (Objects)الكلمة المفتاحية thisالدوال (Methods)الحقول (Attributes)دالة البناء (Constructor)تعدد أشكال دالة البناء (Constructor Overloading)مراجعة - آلة حاسبة بسيطة4الوراثة
الوراثة الأساسية (extends)الكلمة المفتاحية superإعادة تعريف الدوال (@Override)تسلسل المنشئات (Constructor Chaining)كلاس Objectالوراثة الأحادية ومتعددة المستوياتلماذا لا توجد وراثة متعددة للكلاساتمراجعة - هيكلية الموظفين7الدوال الخاصة وفئة Object
دالة toString()equals() و hashCode()دالة clone()compareTo() و Comparableواجهة Comparatorمراجعة - الترتيب المخصص2محددات الوصول والتغليف
نظرة عامة على مستويات الوصولدوال Getter و Setterإخفاء البياناتالكلمة المفتاحية finalمراجعة - مدير الحساب البنكي5تعدد الأشكال (Polymorphism)
أساسيات الـ Method Overloadingالـ Method Overriding (في وقت التشغيل)الـ Upcasting والـ Downcastingمعامل الـ instanceofالفئات والدوال المجردة (Abstract)مراجعة - حاسبة الأشكال8مفاهيم OOP المتقدمة
الـ Composition مقابل الـ Inheritanceالـ Aggregation مقابل الـ Compositionالفئات الداخلية والمتداخلة والمجهولة (Inner, Nested & Anonymous)الـ Enums ودوالهاالـ Records (Java 16+)الـ Sealed Classes (Java 17+)11أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factoryنمط Builderنمط Observerنمط Strategy3خصائص الفئة والعضو الساكن
المتغيرات الخاصة بالكائن مقابل المتغيرات الساكنةالدوال الساكنةالكتل البرمجية الساكنةالثوابت (static final)ملخص - العداد والأدوات المساعدة6الواجهات (Interfaces) والأصناف المجردة (Abstract Classes)
مقدمة في الواجهات (Interfaces)تنفيذ الواجهات (Interfaces)تنفيذ واجهات متعددةالـ Default و Static في الواجهاتالأصناف المجردة مقابل الواجهاتالواجهات الوظيفية (Functional Interfaces)مراجعة - نظام الدفع9الأنواع العامة (Generics)
مقدمة في الأنواع العامةالأصناف العامة (Generic Classes)الدوال العامة (Generic Methods)معاملات الأنواع المقيدةالرموز البديلة (?, extends, super)مراجعة - الحاويات العامة12أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبنمط المكرر