مدخل إلى الواجهات (Interfaces)
مدخل إلى الواجهات (Interfaces)
الواجهة (interface) هي إحدى أقوى أدوات التصميم في Java. في جوهرها هي عقد مُسمَّى: قائمة من توقيعات الدوال التي يجب على أي كلاس يختار الالتزام بهذا العقد تنفيذها. الكلاس يوفّر الـ كيف، والواجهة تُحدّد الـ ماذا.
لقد رأيت سابقًا كيف يُتيح الوراثة لكلاس أن يُعيد استخدام كود كلاس آخر. الواجهات تأخذ زاوية مختلفة — فهي تفصل القدرة عن النسب. لا يحتاج كلاس إلى أن يشترك مع كلاس آخر في سلف مشترك كي تتعامل بقية البرنامج معهما بالطريقة ذاتها؛ يكفي أن يُفي كلاهما بالواجهة نفسها.
الكلمة المفتاحية interface
تُعرَّف الواجهة بالكلمة المفتاحية interface بدلًا من class. جميع تصريحات الدوال بداخلها هي ضمنيًا public abstract — لا داعي لكتابة هذين المُعدِّلَين صراحةً (وإن كان ذلك صحيحًا نحويًا).
هذا هو التعريف بأكمله. لا مُنشئ، ولا حقول مُتغيّرة، ولا أجسام دوال (سنتناول دوال default في درس لاحق). تقول Drawable ببساطة: كل شيء قابل للرسم يجب أن يكون قادرًا على رسم نفسه والإبلاغ عن مساحته.
تطبيق الواجهة (implements)
يوقّع الكلاس على العقد بالكلمة المفتاحية implements. يتحقّق المُترجم بعدها من توفير كل دالة مجرّدة.
@Override ليس إلزاميًا، لكن استخدمه دائمًا. يُخبر المُترجم بأنك تعتزم تنفيذ دالة من الواجهة. إذا أخطأت في الإملاء سيكتشف المُترجم الخطأ فورًا، بدلًا من إنشاء دالة جديدة بصمت لا تُفي بشيء.
البرمجة وفق العقد
هنا يأتي المردود الحقيقي. بما أن Circle وRectangle يُطبّقان Drawable، يمكنك الإشارة إلى أي منهما من خلال متغيّر من النوع Drawable. لا يحتاج الكود الاستدعائي أن يعرف — أو يهتم — أي كلاس ملموس يتعامل معه.
كُتب Canvas مرّة واحدة قبل وجود Circle أو Rectangle. غدًا يمكنك إضافة كلاس Triangle يُطبّق Drawable أيضًا وإدراجه في نفس اللوحة دون تعديل سطر واحد في Canvas. هذا هو مبدأ الفتح/الإغلاق في العمل: مفتوح للتوسعة، مغلق للتعديل.
Drawable d = new Circle(5); بدلًا من Circle d = new Circle(5); كلّما احتاج الكود المتبقّي قدرات Drawable فحسب. يُبقي هذا الاستدعاءات مفصولةً عن التطبيق ويجعل تبديل الكلاسات أمرًا يسيرًا.
الواجهات ونظام الأنواع
الواجهة نوع كامل في Java. يمكنك استخدامها في كل مكان يُتوقّع فيه نوع:
- تصريحات المتغيّرات:
Drawable d = new Circle(3); - معاملات الدوال:
void render(Drawable shape) - أنواع الإرجاع:
Drawable createShape(String kind) - حدود الأنواع العامة:
List<Drawable> shapes
يعمل المعامل instanceof تمامًا كما هو الحال مع الكلاسات:
ماذا يحدث إذا نسيت دالة؟
إذا طبّق كلاس غير مجرّد واجهةً ما دون توفير جميع الدوال المطلوبة، رفض المُترجم ترجمته. هذه السلامة في وقت الترجمة هي إحدى أكبر مزايا الواجهات مقارنةً بالأعراف الأكثر مرونة في وقت التشغيل.
implements للواجهات وextends للكلاس الأب. خلطهما (class Foo extends Drawable) خطأ في الترجمة لأن Drawable واجهة وليست كلاسًا. الصياغة الصحيحة هي class Foo implements Drawable.
لماذا نهتم؟ الحجّة العملية
تخيّل نظام دفع. ربما تبدأ ببطاقات الائتمان، ثم تحتاج لاحقًا إلى دعم PayPal والتحويلات البنكية ومحافظ العملات الرقمية. إذا عرّفت واجهة PaymentProcessor منذ البداية، فإن كود الدفع لا يتحدّث إلا مع ذلك العقد. إضافة وسيلة دفع جديدة تعني كتابة كلاس واحد جديد — دون أي تغيير في منطق الدفع، ودون خطر كسر تدفقات الدفع القائمة.
تبديل Stripe بـ PayPal غدًا هو تغيير سطر واحد في المكان الذي يُنشأ فيه Checkout — كل شيء آخر يبقى كما هو.
الخلاصة
تُعلن الواجهة عن عقد: مجموعة توقيعات الدوال التي يجب على الكلاس الوفاء بها. استخدم interface لتعريفها وimplements للتوقيع على العقد. أعلن عن المتغيّرات والمعاملات بنوع الواجهة — لا بالكلاس الملموس — كي تظل الاستدعاءات مفصولةً عن التطبيق. هذا النمط، الذي يُعرف أحيانًا بـ البرمجة وفق التجريد، هو أساس تصميم Java المرن وقابل للاختبار.
في الدرس التالي سنُقارن الواجهات بالكلاسات المجرّدة ونتعلّم كيف نختار بينهما.