📜 فهم واجهات الـ Interface في جافا: عقد البرمجة الموثوق

مرحباً بك في عالم التجريد الأكثر دقة في جافا! 🎯 إذا كانت الفئات المجردة (abstract class) هي مخططات جزئية، فإن الواجهات (Interfaces) هي عقود برمجية كاملة تُحدد ماذا يجب أن يفعل الكائن، دون أن تهتم أبداً بـ كيف سيفعله. تخيل أن Interface هو قائمة بمتطلبات الوظيفة التي يجب على أي مرشح (كلاس) أن يوافق عليها وينفذها بالكامل ليتم قبوله.


🤝 ما هي الـ Interface في جافا؟

الـ Interface هو نوع مرجعي في جافا، يشبه الكلاس في تعريفه، لكنه يحتوي فقط على توقيعات للطرق (Method Signatures) والمتغيرات الثابتة (static final variables). إنه وعد أو التزام.

خصائص الواجهة الأساسية:

  • جميع الطرق فيها هي public abstract بشكل افتراضي (حتى لو لم تكتب هذه الكلمات).
  • جميع المتغيرات فيها هي public static final بشكل افتراضي (ثوابت).
  • لا يمكنها أن تحتوي على مُنشئ (Constructor).
  • لا يمكنها أن تحتوي على طرق ذات جسم تنفيذي (عدا طرق default و static كما سنرى لاحقاً).

الهدف الرئيسي هو فرض سلوك معين على أي كلاس يريد استخدام هذه الواجهة.


✍️ كيفية تعريف Interface

نستخدم الكلمة المفتاحية interface بدلاً من class. لننشئ واجهة تمثل أي شيء يمكن تشغيله.

// تعريف واجهة تسمى "قابل للتشغيل"
public interface Runnable {
    // ثابت (متغير static final)
    String TYPE = "Electronic Device";

    // توقيع طريقة (سيتم تنفيذها في الكلاس)
    void turnOn();
    void turnOff();
    void setVolume(int level);
}

لاحظ: لم نكتب أجساماً للطرق turnOn() أو turnOff(). لقد حددنا ما الذي يجب وجوده فقط.


✅ كيفية تنفيذ Interface: كلمة implements

لنقول إن كلاس معين يوافق على عقد الـ Interface ويعد بتنفيذ كل بنوده، نستخدم الكلمة المفتاحية implements.

القاعدة الذهبية: أي كلاس implements واجهة، يجب أن يوفر تنفيذاً (جسماً) لكل طريقة مذكورة فيها.

// الكلاس Television يوافق على عقد Runnable وينفذ جميع طرقه
public class Television implements Runnable {
    private boolean isOn;
    private int volume;

    // يجب تنفيذ ALL methods من الواجهة
    @Override
    public void turnOn() {
        isOn = true;
        System.out.println("التلفاز يعمل الآن.");
    }

    @Override
    public void turnOff() {
        isOn = false;
        System.out.println("تم إيقاف التلفاز.");
    }

    @Override
    public void setVolume(int level) {
        this.volume = level;
        System.out.println("مستوى الصوت الآن: " + level);
    }
}
// كلاس آخر يمكنه تنفيذ نفس الواجهة
public class Radio implements Runnable {
    @Override
    public void turnOn() {
        System.out.println("الراديو يبث الآن.");
    }

    @Override
    public void turnOff() {
        System.out.println("إغلاق الراديو.");
    }

    @Override
    public void setVolume(int level) {
        System.out.println("ضبط صوت الراديو على: " + level);
    }
}

الآن، كلا الكلاسين Television و Radio هما من نوع Runnable، لكن لكل منهما تنفيذه الداخلي المختلف لنفس السلوك. هذا هو تجريد الواجهة في أبهى صوره! ✨


🔄 الوراثة المتعددة للواجهات (Multiple Inheritance)

أهم ميزة للـ Interface في جافا هي السماح للكلاس بتنفيذ أكثر من واجهة واحدة. هذا يحل مشكلة عدم تعدد الوراثة في الفئات العادية.

// واجهة أخرى
public interface Connectable {
    void connectToWifi(String networkName);
    void disconnect();
}

// الآن يمكن للكلاس تنفيذ أكثر من واجهة
public class SmartTV implements Runnable, Connectable {
    // يجب تنفيذ جميع طرق Runnable
    @Override
    public void turnOn() { System.out.println("Smart TV ON"); }
    @Override
    public void turnOff() { System.out.println("Smart TV OFF"); }
    @Override
    public void setVolume(int level) { System.out.println("Volume: " + level); }

    // ويجب تنفيذ جميع طرق Connectable
    @Override
    public void connectToWifi(String networkName) {
        System.out.println("Connected to: " + networkName);
    }
    @Override
    public void disconnect() {
        System.out.println("Disconnected from WiFi.");
    }
}

يستطيع SmartTV الآن الالتزام بعقدين مختلفين، مما يمنحه مرونة هائلة.


⚙️ طرق الـ Default و الـ Static في الواجهات (من جافا 8 فما فوق)

لجعل الواجهات أكثر مرونة، أضافت جافا 8 نوعين من الطرق التي يمكن أن يكون لها جسم تنفيذي داخل الـ Interface.

الطرق الافتراضية (default methods):

  • تسمح بإضافة طريقة بتنفيذ افتراضي إلى الواجهة دون كسر الأكواد القديمة التي تنفذها.
  • الكلاس المنفذ للواجهة يمكنه استخدام التنفيذ الافتراضي أو تجاوزه (@Override).
public interface Runnable {
    void turnOn();
    void turnOff();

    // طريقة default جديدة لها تنفيذ افتراضي
    default void restart() {
        turnOff();
        System.out.println("wait a minute...");
        turnOn();
        System.out.println("restarted!");
    }
}

الآن، كل كلاس ينفذ Runnable يحصل على طريقة restart() مجاناً دون أن يضطر لكتابتها، ما لم يرد تغيير سلوكها.

الطرق الثابتة (static methods):

  • طرق تنتمي للواجهة نفسها، ولا يمكن للكلاس المنفذ تجاوزها.
  • نستدعيها مباشرة باسم الواجهة InterfaceName.staticMethod().
public interface Runnable {
    // ... طرق أخرى

    static String getManufacturerInfo() {
        return "جميع الأجهزة القابلة للتشغيل تخضع لمواصفات السلامة العالمية.";
    }
}

// الاستدعاء
String info = Runnable.getManufacturerInfo(); // ✅ صحيح
// Television.getManufacturerInfo(); // ❌ خطأ! الطرق static تُستدعى من الواجهة فقط

🆚 Interface مقابل Abstract Class: متى نستخدم أياً منهما؟

الميزة Interface Abstract Class
الطرق توقيعات فقط (إلا default/static) يمكن أن تحتوي على طرق مجردة وعادية
المتغيرات static final فقط (ثوابت) أي نوع من المتغيرات
الوراثة وراثة متعددة (كلاس يمكنه implements أكثر من واجهة) وراثة وحيدة (كلاس يمكنه extends كلاس مجرد واحد فقط)
المنشئ لا يوجد يوجد
المبدأ "يمكن أن يفعل" (سلوك) "هو" (نوع/هوية) + سلوك

قاعدة بسيطة:

  • استخدم interface عندما تريد فرض سلوك محدد على مجموعات غير مرتبطة من الكلاسات (مثل Television, Radio, Car جميعها Runnable).
  • استخدم abstract class عندما يكون لديك مجموعة من الكلاسات المتقاربة جداً في النوع وتشارك في كود أساسي مشترك (مثل Dog, Cat كلاهما extends Animal).

💡 مثال عملي متكامل

لنطبق ما تعلمناه في مثال يربط المفاهيم:

// الواجهة الرئيسية
interface Shape {
    double calculateArea(); // كل شكل يجب أن يحسب مساحته

    default void printInfo() {
        System.out.println("This is a geometric shape.");
    }

    static String getGeometryType() {
        return "Euclidean geometry";
    }
}

// واجهة إضافية
interface Drawable {
    void draw();
}

// كلاس ينفذ واجهتين
class Circle implements Shape, Drawable {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double calculateArea() {
        return Math.PI * radius * radius;
    }

    @Override
    public void draw() {
        System.out.println("Drawing a circle with radius: " + radius);
    }

    @Override // تجاوز الـ default method (اختياري)
    public void printInfo() {
        System.out.println("This is a circle.");
    }
}

// الاستخدام في main
public class Main {
    public static void main(String[] args) {
        Circle c = new Circle(5.0);

        // طرق من كلاس Circle
        System.out.println("Area: " + c.calculateArea());
        c.draw();

        // default method (مُتجاوزة)
        c.printInfo();

        // static method (من الواجهة مباشرة)
        System.out.println(Shape.getGeometryType());

        // polymorphism باستخدام الواجهة
        Shape myShape = c; // ✅ Circle هو Shape
        Drawable myDrawable = c; // ✅ Circle هو Drawable
    }
}