الواجهات والأصناف المجرّدة

مراجعة الفئات المجردة

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

مراجعة الفئات المجردة

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

ما الذي تعنيه الكلمة المفتاحية abstract فعلًا

وضع abstract على فئة يفعل شيئين في آنٍ واحد:

  • يحظر الإنشاء المباشر — لا يمكن لأحد استدعاء new Shape() إذا كانت Shape مجردة.
  • يتيح الدوال المجردة — تصريحات دوال بلا جسم تُلزم الفئات الفرعية بتنفيذها.

هذان الحكمان يعملان معًا: لا يمكنك إنشاء نسخة من فئة تحتوي على دوال غير منفَّذة، فالمُصرِّف يفرض العقد تلقائيًا.

// فئة مجردة — لا يمكن إنشاء نسخة منها مباشرةً public abstract class Shape { // دالة مجردة — بلا جسم public abstract double area(); // دالة ملموسة — لها جسم، تُورَث كما هي public String describe() { return "أنا شكل مساحتي " + area(); } } public class Circle extends Shape { private final double radius; public Circle(double radius) { this.radius = radius; } // يجب تنفيذ العقد المجرد @Override public double area() { return Math.PI * radius * radius; } }

لاحظ أن describe() منفَّذة بالكامل في الفئة المجردة وتستدعي area(). في وقت التشغيل تُوجَّه الاستدعاء إلى Circle.area(). هذا هو نمط Template Method في أبسط صوره: الفئة الأساسية ترسم هيكل الخوارزمية، وتملأ الفئات الفرعية الفراغات.

الدوال المجردة مقابل الملموسة — اختيار ما تُوفّره

كل دالة في فئة مجردة إما مجردة (مؤجَّلة) أو ملموسة (مشتركة). القاعدة الإرشادية:

  • اجعل الدالة مجردة عندما تحتاج كل فئة فرعية فعلًا إلى تنفيذ مختلف ولا يوجد سلوك افتراضي منطقي.
  • اجعل الدالة ملموسة عندما تشترك معظم الفئات الفرعية في نفس السلوك، أو عندما تريد بناء منطق قابل لإعادة الاستخدام يستدعي الأجزاء المجردة.
public abstract class Report { // دالة قالبية — تُنسّق سير العمل public final void generate() { fetchData(); String body = format(); // مجردة — تتفاوت حسب نوع التقرير deliver(body); } protected abstract String format(); // كل نوع تقرير يُحدّد تخطيطه private void fetchData() { System.out.println("جلب البيانات من قاعدة البيانات..."); } private void deliver(String body) { System.out.println("إرسال التقرير:\n" + body); } } public class PdfReport extends Report { @Override protected String format() { return "[PDF] ملخص المبيعات: إيرادات الربع الثاني ارتفعت 12%"; } } public class CsvReport extends Report { @Override protected String format() { return "month,revenue\nJune,1200000"; } }
ملاحظة حول final مع الدوال القالبية: تعليم الدالة المُنسِّقة بـ final يمنع الفئات الفرعية من كسر سير العمل عن طريق الخطأ. دوال الخطّاف المجردة هي ما تُخصّصه الفئات الفرعية — أما الهيكل فيبقى ثابتًا.

التنفيذات الجزئية وحقن المُنشئ

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

public abstract class Animal { private final String name; // الفئات الفرعية تستدعي super() لتمرير الاسم protected Animal(String name) { this.name = name; } public String getName() { return name; } // مجردة — كل حيوان يُصدر صوته الخاص public abstract String sound(); // ملموسة — نفس منطق التحية للجميع public void greet() { System.out.println(getName() + " يقول: " + sound()); } } public class Dog extends Animal { public Dog(String name) { super(name); } @Override public String sound() { return "هاو"; } } public class Cat extends Animal { public Cat(String name) { super(name); } @Override public String sound() { return "مياو"; } } // الاستخدام Animal dog = new Dog("ريكس"); dog.greet(); // ريكس يقول: هاو
استخدم protected لمُنشئات الفئة المجردة عندما ينبغي للفئة أن لا تُبنى إلا من خلال الفئات الفرعية. public يعمل أيضًا (المُصرِّف يمنع الإنشاء المباشر على أي حال)، لكن protected يُعبّر عن النية بصورة أوضح.

متى تكون الفئة المجردة الأداة المناسبة

اطرح على نفسك هذه الأسئلة:

  1. هل توجد علاقة "is-a" حقيقية؟ الكلب حيوان؛ الفئة المجردة تُجسّد هذا النوع الأب.
  2. هل تشترك الفئات الفرعية في حالة فعلية؟ إذا احتاجت كل فئة فرعية إلى حقل name، ضعه في الفئة المجردة.
  3. هل يوجد هيكل خوارزمية مشترك؟ الدوال القالبية تتألّق عندما تكون الخطوات متشابهة لكن بعضها يتفاوت.
  4. هل يكفي الوراثة الأحادية؟ الفئة يمكنها مدّ أب واحد فقط. إذا احتجت تأليف أنواع متعددة، ستحتاج إلى واجهات (الدرس التالي).
لا تستخدم فئة مجردة فقط لمنع الإنشاء. إذا لم يكن لديك حالة مشتركة ولا دوال ملموسة، فالواجهة على الأرجح الخيار الأفضل. الفئات المجردة التي لا تحتوي إلا على دوال مجردة تُهدر فرصة وراثة ثمينة.

ضمانات المُصرِّف

يفرض مُصرِّف Java العقد المجرد في كل خطوة:

  • محاولة إنشاء نسخة من فئة مجردة هي خطأ في وقت التصريف، ليس في وقت التشغيل.
  • فئة فرعية لا تنفّذ جميع الدوال المجردة يجب هي نفسها أن تُعلَن abstract.
  • يمكنك الاحتفاظ بمرجع من نوع Shape أو Animal — تعدّدية الأشكال تعمل بصورة طبيعية؛ المحظور فقط هو الإنشاء المباشر.
Shape s = new Shape(); // خطأ في التصريف: Shape مجردة Shape c = new Circle(5); // صحيح — Circle ملموسة System.out.println(c.describe()); // تستدعي Circle.area() في وقت التشغيل

الخلاصة

الفئة المجردة نوعٌ منفَّذ جزئيًا يُلزم الفئات الفرعية باستكمال العقد مع مشاركة الكود القابل لإعادة الاستخدام. استخدمها عندما يكون لديك تسلسل هرمي حقيقي مع حالة أو سلوك مشترك، وعندما يظهر نمط الدالة القالبية بصورة طبيعية. في الدرس التالي نُقدّم الواجهات — آليةً مكمّلة (وأكثر شيوعًا في الغالب) للتعبير عن العقود دون وراثة.