Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

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

جزء من قسم البرمجة كائنية التوجه في رحلة Java على Coddy. الدرس 38 من 87.

الآن بعد أن فهمت كلاً من abstract classes والواجهات، يبرز سؤال شائع: متى ينبغي أن تستخدم أحدهما بدلاً من الآخر؟ يعتمد الاختيار على ما تحاول نمذجته.

استخدم abstract class عندما تشترك classes في أساس مشترك مع حالة مشتركة أو behavior مشترك. يمكن أن تحتوي Abstract classes على متغيرات مثيل، ومنشئات، ومزيج من الأساليب abstract وconcrete:

public abstract class Animal {
    protected String name;  // حالة مشتركة
    
    public Animal(String name) {
        this.name = name;
    }
    
    public void sleep() {  // سلوك مشترك
        System.out.println(name + " is sleeping");
    }
    
    public abstract void makeSound();  // يجب تنفيذه
}

استخدم واجهة عندما تريد تعريف capability يمكن لـ classes غير المرتبطة مشاركتها. تركز الواجهات على ما يمكن للكائن فعله، وليس على ماهيته:

public interface Flyable {
    void fly();
}

// يمكن للفئات غير المرتبطة مشاركة هذه القدرة
class Bird extends Animal implements Flyable { ... }
class Airplane implements Flyable { ... }
class Drone implements Flyable { ... }

إليك مقارنة سريعة:

الميزةAbstract Classالواجهة
متغيرات النسخةنعمالثوابت فقط
المنشئاتنعملا
التوريث المتعددلا (‏extends واحد)نعم (‏implements متعددة)
معدِّلات الوصولأيٌّ منهاPublic فقط (للطرائق abstract)

إرشاد عملي: إذا وجدت نفسك تنشئ فئة abstract تحتوي فقط على أساليب abstract ولا تتضمن أي حالة، فمن المرجح أن تكون الواجهة خيارًا أفضل.

challenge icon

التحدي

سهل

لنبنِ نظام مركبات يوضّح متى نستخدم الفئات abstract مقابل الواجهات. ستنمذج مركبات تشترك في الحالة والسلوك المشتركين من خلال فئة abstract، مع إضافة إمكانات اختيارية من خلال الواجهات.

ستنظّم التعليمات البرمجية عبر خمسة ملفات:

  • Vehicle.java: أنشئ فئة abstract تعمل كأساس لجميع المركبات. لكل مركبة حقل brand من النوع (String) وحقل year من النوع (int). أضف constructor لتهيئة كلا الحقلين، وgetter methods لكل منهما، وmethod abstract باسم startEngine() تُرجع String. أضف أيضًا method concrete باسم getInfo() تُرجع: [brand] ([year]). هذه حالة مثالية لاستخدام فئة abstract: فالمركبات تشترك في الحالة (brand وyear) وبعض السلوك (getInfo)، لكن كل مركبة تبدأ محركها بطريقة مختلفة.
  • Convertible.java: عرّف interface للمركبات التي يمكنها تحويل سقفها. هذه الإمكانية ليست مرتبطة بما تكونه المركبة is؛ بل هي شيء تستطيع مركبات معينة can do. صرّح بعمليتين: openRoof() وcloseRoof()، وكلتاهما تُرجعان String.
  • Car.java: أنشئ فئة تمتد من Vehicle وتطبّق Convertible. لدى Car حقل إضافي باسم numDoors من النوع (int). استخدم super لتهيئة الحقول الموروثة. نفّذ startEngine() لتُرجع: [brand] car engine started. نفّذ openRoof() لتُرجع: [brand] roof opening، وcloseRoof() لتُرجع: [brand] roof closing.
  • Motorcycle.java: أنشئ فئة تمتد من Vehicle لكنها لا تطبّق Convertible: فالدراجات النارية لا تملك أسقفًا! لدى Motorcycle حقل باسم hasSidecar من النوع (boolean). نفّذ startEngine() لتُرجع: [brand] motorcycle engine roaring.
  • Main.java: اجمع كل شيء معًا لعرض الفرق بين فئات abstract والواجهات. ستتلقى أربع مدخلات: brand السيارة، وyear السيارة، وbrand الدراجة النارية، وyear الدراجة النارية.

    أنشئ Car (بـ 4 أبواب) وMotorcycle (من دون عربة جانبية). أولًا، اعرض السلوك المشترك لفئة abstract من خلال طباعة getInfo() لكلا المركبتين. كلتاهما ترثان هذا السلوك من Vehicle. ثم اطبع نتيجة startEngine() لكل منهما: لاحظ كيف ينفّذ كل نوع من المركبات ذلك بطريقة مختلفة.

    أخيرًا، اعرض إمكانية الواجهة: بما أن Car وحدها تطبّق Convertible، استدعِ واطبع openRoof() متبوعًا بـ closeRoof() على السيارة فقط.

ستتلقى أربع مدخلات: brand السيارة (String)، وyear السيارة (int)، وbrand الدراجة النارية (String)، وyear الدراجة النارية (int).

يجب أن يُظهر ناتجك ستة أسطر إجمالًا. لاحظ كيف تشترك المركبتان في حالة فئة abstract وطريقة getInfo()، لكن Car وحدها تملك إمكانية Convertible. يوضّح هذا متى تستخدم كل نهج!

جرّب بنفسك

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // قراءة المدخلات
        String carBrand = scanner.nextLine();
        int carYear = scanner.nextInt();
        scanner.nextLine(); // استهلاك السطر الجديد
        String motorcycleBrand = scanner.nextLine();
        int motorcycleYear = scanner.nextInt();
        
        // TODO: إنشاء Car بـ 4 أبواب
        
        // TODO: إنشاء Motorcycle بدون عربة جانبية (false)
        
        // TODO: طباعة getInfo() لكلا المركبتين (يوضح سلوك abstract class المشترك)
        
        // TODO: طباعة startEngine() لكلا المركبتين (يوضح التنفيذات المختلفة)
        
        // TODO: طباعة openRoof() و closeRoof() للسيارة فقط (يوضح قدرات interface)
    }
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس البرمجة كائنية التوجه

تدرّب بنفسك: مترجم Java عبر الإنترنت