ما هي الفئة المجردة
تُعرِّف الواجهة سلوكاً دون حالة. والفئة العادية مكتملة التنفيذ ويمكن إنشاء كائن منها. أما الفئة المجردة فتقع بين الاثنتين: يمكنها أن تحمل حقولاً ومُنشئات ودوالاً مكتملة مثل الفئة العادية، لكنها تستطيع أيضاً أن تترك بعض الدوال دون تنفيذ وتمنع إنشاء كائن منها مباشرة. وتضع عليها علامة بالكلمة المفتاحية abstract.
الفكرة هي تجميع كل ما تشترك فيه الفئات الفرعية في مكان واحد، مع إجبار كل فئة فرعية على ملء الأجزاء التي تختلف فعلاً.
تُعرِّف Animal الدالة getName() مرة واحدة لكل الفئات الفرعية، وتُعلن sound() على أنها abstract - دالة لها توقيع لكن بلا جسم، ويجب على Dog أن توفّرها.
لا يمكنك إنشاء كائن من فئة مجردة
بما أن الفئة المجردة قد تحتوي على دوال غير مكتملة، فإن إنشاءها مباشرة سيترك لديك كائناً ناقصاً. والمترجم يرفض ذلك:
Animal a = new Animal("???"); // error: Animal is abstract; cannot be instantiated
أنت تُنشئ دائماً كائناً من فئة فرعية ملموسة - فئة نفَّذت كل دالة مجردة. ويمكن بعد ذلك الاحتفاظ بكائن هذه الفئة الفرعية في متغير من النوع المجرد، وهذه هي بالضبط طريقة استخدام التجريد.
الدوال المجردة تُجبر الفئات الفرعية على الحسم
الدالة abstract وعدٌ يجب على الفئة الفرعية أن تفي به. فإذا نسيت فئة فرعية تنفيذ إحداها، صارت تلك الفئة الفرعية نفسها مجردة، وأخبرك المترجم بذلك. هذه هي الأداة الأساسية للفئة المجردة: فهي تضمن وجود سلوك معين دون أن تفرض ما يفعله.
تُكتب describe() مرة واحدة في Shape، ومع ذلك تستدعي دالة area() الخاصة بكل فئة فرعية. الفئة المجردة توفّر الهيكل المشترك؛ والفئات الفرعية توفّر التفاصيل.
الحالة المشتركة والمُنشئات
على خلاف الواجهة التقليدية، يمكن للفئة المجردة أن تحمل حقول نسخة وأن تُعرِّف مُنشئات. والمُنشئ لا يُنشئ بمفرده كائن Animal أو Shape أبداً - بل يُنفَّذ عبر super(...) عند إنشاء فئة فرعية، فيُهيّئ الحالة المشتركة.
الحقل balance والدالتان deposit وapplyInterest تعيش جميعها في مكان واحد. ولا يُترك مجرداً سوى السياسة التي تختلف فعلاً - وهي interestRate(). ويجب على مُنشئ الفئة الفرعية أن يستدعي super(...) لتهيئة تلك الحالة الموروثة.
مزلق: الخلط بين abstract وfinal
abstract وfinal متناقضتان. فالدالة abstract تطلب إعادة تعريف (override)، بينما الدالة final تمنعه. ووضع العلامتين معاً على الشيء نفسه - أو جعل فئة مجردة final - خطأٌ في الترجمة. وتذكّر أيضاً أن الفئة المجردة قد لا تحتوي على أي دالة مجردة على الإطلاق: فإعلان فئة على أنها abstract لمجرد منع إنشاء كائن منها أمرٌ مشروع، ومفيد أحياناً للأنواع الأساسية التي لا تريد سوى أن تُورَّث.
abstract final class Bad { } // error: abstract and final conflict
abstract class Base {
abstract final void f(); // error: an abstract method can't be final
}
الفئة المجردة مقابل الواجهة
تتداخل الاثنتان، لذا يتوقف الاختيار على ما تحتاج إلى مشاركته:
- الفئة المجردة - استخدمها لفئات وثيقة الصلة تتشارك الحالة والكود. فكل من
SavingsوCheckingيرثAccount، فيرث الحقلbalanceومنطقdeposit. والفئة لا ترث سوى واحدة. - الواجهة - استخدمها من أجل قدرة يمكن أن تتشاركها فئات غير مترابطة. فبإمكان كل من
BirdوAirplaneأن يكونFlyableدون أن يتشاركا أي تنفيذ. والفئة يمكنها أن تُنفِّذ عدة واجهات.
ثمة نمط شائع يجمع بينهما: واجهة تُعرِّف العقد، وفئة مجردة تُنفِّذ الكود المتكرر، حتى لا تملأ الفئات الفرعية الملموسة سوى ما هو فريد.
التالي: تعدد الأشكال
لاحظ أننا في كل مثال أعلاه احتفظنا بكائن من فئة فرعية في متغير من النوع المجرد، ثم استدعينا دالة فحصلنا تلقائياً على سلوك الفئة الفرعية. هذه القدرة الواحدة - نوع مرجعي واحد، وسلوكيات متعددة في وقت التشغيل - هي تعدد الأشكال (polymorphism)، وهي ما يجعل الفئات المجردة والواجهات تؤتي ثمارها. وهذا هو موضوع الصفحة التالية.
الأسئلة الشائعة
ما هي الفئة المجردة في جافا؟
الفئة المجردة هي فئة مُعرَّفة بالكلمة المفتاحية abstract ولا يمكن إنشاء كائن منها بمفردها. الغرض منها أن تُورَّث (extend). ويمكنها أن تمزج دوالاً وحقولاً مكتملة التنفيذ (حالة وكود مشتركان للفئات الفرعية) مع دوال abstract بلا جسم؛ وتنفيذ هذه الدوال يصبح مسؤولية كل فئة فرعية.
هل يمكن إنشاء كائن من فئة مجردة في جافا؟
لا. ينتج عن new AbstractType() خطأ في الترجمة (compile error) لأن الفئة المجردة قد تحتوي على دوال غير مُنفَّذة (abstract)، ما يجعل الكائن ناقصاً. أنت تُنشئ كائناً من فئة فرعية ملموسة (concrete) تُنفِّذ كل الدوال المجردة، ثم تخزّنه في متغير من النوع المجرد.
ما الفرق بين الفئة المجردة والواجهة في جافا؟
يمكن أن تحتوي الفئة المجردة على حقول نسخة ومُنشئات ومنطق مُنفَّذ جزئياً، لكن الفئة لا يمكنها أن ترث سوى فئة واحدة. أما الواجهة فتُعرِّف سلوكاً دون حالة نسخة، ويمكن للفئة أن تُنفِّذ عدة واجهات. استخدم الفئة المجردة لمشاركة الحالة والكود بين فئات فرعية وثيقة الصلة؛ واستخدم الواجهة لمنح فئات غير مترابطة قدرةً مشتركة.