الواجهة مقابل الفئة المجردة
الواجهة مقابل الفئة المجردة
بعد أن تعرّفت على شكل كلٍّ من الأداتين، يبقى السؤال العملي: أيّهما تختار؟ تعتمد الإجابة على طبيعة علاقتين مختلفتين: ما الذي يكونه النوع في مقابل ما الذي يستطيع فعله.
الفارق الجوهري
تُنمذج الفئة المجردة علاقة هو-نوع-من. فهي تمثّل نوعًا أبًا حقيقيًا يتشارك الحالة والسلوك مع أبنائه. أمّا الواجهة فتُنمذج علاقة قادر-على (أو يتصرّف-كـ). فهي تُعلن عن قدرة يستطيع أيّ صنف غير مترابط أن يختار اعتمادها.
- الـ
Dogهو نوع منAnimal— استخدم فئة مجردة. - الـ
Dogقادر على كونهTrainable— استخدم واجهة. - الـ
Robotقادر هو أيضًا على كونهTrainableرغم أنه ليسAnimal.
الفروق التقنية الرئيسية
إلى جانب النموذج المفاهيمي، ثمّة قواعد صارمة تُقيّد اختيارك:
- الوراثة المتعددة: يسمح Java للصنف بتطبيق واجهات متعددة لكنّه لا يسمح له بتوسيع سوى فئة مجردة واحدة. هذا القيد وحده هو السبب الأكثر شيوعًا لتفضيل الواجهات.
- الحالة المُخزَّنة (Instance State): تستطيع الفئات المجردة أن تمتلك حقول نسخة (حالة مُخزَّنة). أمّا الواجهات فلا تحتوي إلّا على ثوابت
public static final— ولا تحمل أيّ بيانات خاصة بالكائن. - المُنشِئات (Constructors): يمكن للفئات المجردة تعريف مُنشِئات لتهيئة الحقول المشتركة. أمّا الواجهات فلا مُنشِئات لها.
- محدّدات الوصول: يمكن أن تكون أعضاء الفئة المجردة
privateأوprotectedأوpublic. أعضاء الواجهةpublicافتراضيًا (رغم إضافة دعمprivateللتوابع المساعدة في Java 9).
مثال مقارن
لنأخذ تطبيق رسم. الـ Shape هو أبٌ حقيقي — يحمل حقل اللون ومنهج describe() المشترك الذي يرثه كل شكل. أمّا Resizable وExportable فهما قدرتان لا تحتاجهما إلّا بعض الأشكال.
لاحظ أنّ Circle وRectangle يتشاركان حقل color ومنهج describe() عبر الفئة المجردة — تلك الحالة تعيش في مكان واحد. أمّا القدرات (Resizable، Exportable) فتُضاف باستقلالية لكل صنف دون المساس بالتدرّج الهرمي.
حين تُجبرك الحالة المشتركة على الاختيار
إن احتاج تصميمك فعلًا إلى حقل مُشترك قابل للتغيير، فيجب عليك استخدام فئة مجردة. لا تستطيع الواجهات حمله. محاولة محاكاة الحالة المشتركة عبر ثوابت ثابتة في الواجهات هو خطأ في التصميم — الثوابت ليست بديلًا عن بيانات الكائن.
عامل "تطوّر الواجهة البرمجية"
إن كنت مالكًا لمكتبة وتحتاج إضافة منهج لاحقًا، فالأثر يختلف:
- الفئة المجردة: أضف منهجًا مُنفَّذًا — تَرِثه الأصناف الفرعية الحالية مجانًا دون كسر أيّ شيء.
- الواجهة (قبل Java 8): إضافة أيّ منهج كانت ستُخلّ بكل مُنفِّذ. أدخل Java 8 مناهج
defaultتحديدًا لحلّ هذه المشكلة — يَرِث المُنفِّذون الحاليون القيمة الافتراضية تلقائيًا.
default للواجهات البرمجية العامة. يُبقي ذلك التصميم مرنًا (تعدّد التطبيقات، وراثة متعددة) ويسمح لك بتطوير الواجهة دون إجبار كل مُنفِّذ على التحديث الفوري.
دليل القرار السريع
- تحتاج مشاركة حقول نسخة بين الأصناف الفرعية؟ ← فئة مجردة.
- تحتاج مُنشِئًا لضمان التهيئة؟ ← فئة مجردة.
- تريد صنفًا يرث من مصادر متعددة؟ ← واجهات (يمكن تطبيق العديد منها).
- تُنمذج قدرة تشترك فيها أنواع غير مترابطة؟ ← واجهة.
- تنشر واجهة برمجية لمكتبة يجب أن تبقى متوافقة مع الإصدارات السابقة؟ ← واجهة مع مناهج
default.
default، حتى لا تُقيّد المُستدعين بسلسلة وراثة واحدة دون مبرّر.
الخلاصة
الفئات المجردة والواجهات أداوتان مُتكاملتان لا مُتنافستان. استخدم الفئة المجردة حين تحتاج مشاركة الحالة أو فرض تدرّج نوعي صارم. استخدم الواجهة حين تُعرّف قدرةً يستطيع اعتمادها أصناف متنوّعة وغير مترابطة — وتذكّر أنّ صنفًا واحدًا يمكنه تطبيق واجهات كثيرة. في الممارسة الفعلية، يعتمد كود Java الحديث اعتمادًا كبيرًا على الواجهات ويحتفظ بالفئات المجردة للحالات التي تنتمي فيها الحالة القابلة للتغيير أو منطق المُنشِئ فعلًا إلى الأب.