الوراثة وتعدّد الأشكال

تجاوز الدوال وتعليق @Override

15 دقيقة الدرس 3 من 14

تجاوز الدوال وتعليق @Override

يُتيح الوراثةُ للصنف الفرعي إعادةَ استخدام كود الصنف الأب. لكن ماذا لو احتاج الفرعي إلى تغيير آلية عمل دالة موروثة؟ هنا يأتي دور تجاوز الدوال (Method Overriding). تكتب تنفيذًا جديدًا للدالة الأبوية داخل الصنف الفرعي، فيستدعي Java نسخة الفرعي في كل مرة يكون فيها الكائن من نوع الفرعي.

ما هو تجاوز الدوال؟

عندما يُعرِّف الصنف الفرعي تنفيذه الخاص لدالة موجودة بالفعل في الصنف الأب، يُسمّى ذلك تجاوزًا. يجب أن تحمل الدالة في الفرعي الاسم ذاته، وقائمة المعاملات ذاتها، ونوع إرجاع متوافقًا مع تلك الموجودة في الأب.

class Animal { public String speak() { return "..."; } } class Dog extends Animal { public String speak() { // تتجاوز Animal.speak() return "Woof!"; } }

عند استدعاء speak() على كائن Dog، يُشغِّل Java نسخة Dog لا نسخة Animal — حتى لو كان نوع المتغير Animal. (ستستكشف ذلك بعمق في درس تعدد الأشكال.)

تعليق @Override

يُتيح لك Java وضع تعليق @Override على الدالة المتجاوِزة. هذا اختياري في وقت التشغيل، لكنه موصى به بشدة.

class Dog extends Animal { @Override public String speak() { return "Woof!"; } }
لماذا تستخدم @Override؟ يُخبر المُترجِمَ بأنك تقصد تجاوز دالة في الأب. فإن أخطأت في الاسم، أو غيّرت المعاملات سهوًا، أو لم تعد الدالة الأبوية موجودة، يُبلِّغ المُترجم عن خطأ فورًا بدلًا من إنشاء دالة جديدة غير مقصودة. اكتبها دائمًا.

القواعد الأربع للتجاوز

  1. الاسم ذاته — يجب أن يتطابق تمامًا.
  2. قائمة المعاملات ذاتها — العدد والأنواع والترتيب. المعاملات المختلفة تنشئ دالة زائدة تحميل جديدة، لا تجاوزًا.
  3. نوع إرجاع متوافق — إما النوع ذاته أو نوع فرعي منه (الإرجاع المتغاير؛ انظر أدناه).
  4. معدِّل الوصول لا يجوز أن يكون أكثر تقييدًا — إن أعلن الأب public، لا يجوز للفرعي الإعلان عن protected أو private. يمكن الإبقاء عليه public أو توسيعه، لكن لا تضييقه أبدًا.
التجاوز مقابل التحميل الزائد: من السهل الخلط بينهما. التحميل الزائد (Overloading) يعني الاسم ذاته مع معاملات مختلفة — ولا يحتاج وراثة. أما التجاوز (Overriding) فيعني الاسم ذاته والمعاملات ذاتها في صنف فرعي. إن وضعت @Override على دالة تختلف فقط في المعاملات، سيكتشف المُترجم الخطأ.

أنواع الإرجاع المتغايرة

منذ Java 5، يجوز للدالة المتجاوِزة أن تُرجع نوعًا فرعيًا من نوع إرجاع الأب. يُسمّى هذا نوع الإرجاع المتغاير (Covariant Return Type). يُفيد ذلك حين يُرجع الأب نوعًا عامًا والفرعي يستطيع ضمان شيء أكثر تحديدًا.

class Shape { public Shape copy() { return new Shape(); } } class Circle extends Shape { @Override public Circle copy() { // Circle نوع فرعي من Shape — مسموح return new Circle(); } }

يحصل مستدعو Circle.copy() على Circle مباشرةً دون أي تحويل نوع. يُحسِّن ذلك سلامة الأنواع دون كسر التوافق.

تتألق أنواع الإرجاع المتغايرة في الأصناف ذات أسلوب البناء (Builder) ودوال المصنع (Factory Methods). إرجاع نوع أكثر تحديدًا يُبقي المستدعي على فائدة النوع الملموس بينما تظل الواجهة البرمجية ضمن التسلسل الهرمي للأب.

ما الذي لا يمكن تجاوزه

  • الدوال private — غير مرئية للأصناف الفرعية، لذا لا يمكن تجاوزها (بل إخفاؤها فقط).
  • الدوال static — تُحلَّل في وقت التجميع لا في وقت التشغيل. يمكن للفرعي تعريف دالة ساكنة بالتوقيع ذاته، لكن ذلك يُسمّى إخفاء الدوال (Method Hiding)، لا تجاوزًا.
  • الدوال final — تحظر الكلمة المفتاحية final صراحةً أي تجاوز. ستتعلم عن final في درس لاحق.

مثال كامل قابل للتشغيل

class Employee { private String name; public Employee(String name) { this.name = name; } public String getName() { return name; } public double calculateBonus(double salary) { return salary * 0.05; // مكافأة افتراضية 5% } } class Manager extends Employee { public Manager(String name) { super(name); } @Override public double calculateBonus(double salary) { return salary * 0.15; // المديرون يحصلون على 15% } } class Intern extends Employee { public Intern(String name) { super(name); } @Override public double calculateBonus(double salary) { return 0; // المتدربون لا يحصلون على مكافأة } } // الاستخدام Employee e1 = new Manager("Alice"); Employee e2 = new Intern("Bob"); System.out.println(e1.getName() + ": " + e1.calculateBonus(80_000)); // 12000.0 System.out.println(e2.getName() + ": " + e2.calculateBonus(30_000)); // 0.0

يُعرِّف كل صنف ما تعنيه المكافأة لنوعه الخاص. استدعاء الدالة ذاتها ينتج نتائج مختلفة تبعًا للكائن الفعلي — هذه هي قوة التجاوز جنبًا إلى جنب مع تعدد الأشكال.

الخلاصة

  • يُتيح التجاوز للصنف الفرعي استبدال تنفيذ دالة أبوية.
  • أضف @Override دائمًا — يكتشف المُترجم الأخطاء الإملائية والتعارضات نيابةً عنك.
  • التزم بالقواعد الأربع: الاسم ذاته، المعاملات ذاتها، نوع إرجاع متوافق (متغاير)، ومعدِّل وصول مساوٍ أو أوسع.
  • أنواع الإرجاع المتغايرة تُتيح إرجاع نوع أكثر تحديدًا دون كسر العقد.
  • الدوال private وstatic وfinal لا يمكن تجاوزها.