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

الأصناف المجردة (Abstract Classes)

جزء من قسم Object Oriented Programming في رحلة C++ على Coddy — الدرس 60 من 104.

تُعد الفئة المجردة أي فئة تحتوي على دالة افتراضية بحتة واحدة على الأقل. وبسبب امتلاكها لواجهة غير مكتملة، لا يمكنك إنشاء كائنات من فئة مجردة بشكل مباشر - فهي موجودة فقط ليتم الوراثة منها.

class Shape {
public:
    virtual double area() = 0;      // دالة افتراضية بحتة - تجعل Shape مجرداً
    virtual double perimeter() = 0; // دالة افتراضية بحتة أخرى
    
    void printInfo() {              // طريقة عادية - لها تنفيذ
        std::cout << "Area: " << area() << std::endl;
    }
    
    virtual ~Shape() = default;
};

Shape s;  // خطأ: لا يمكن إنشاء كائن من فئة مجردة

يمكن أن تحتوي الأصناف المجردة (Abstract classes) على مزيج من الدوال الافتراضية البحتة (بدون تنفيذ)، والدوال الافتراضية العادية (مع تنفيذ افتراضي)، والدوال غير الافتراضية. يتيح لك ذلك تحديد سلوك مشترك مع مطالبة الأصناف المشتقة بتنفيذ وظائف محددة.

class Circle : public Shape {
    double radius;
public:
    Circle(double r) : radius(r) {}
    
    double area() override { return 3.14159 * radius * radius; }
    double perimeter() override { return 2 * 3.14159 * radius; }
};

Circle c(5.0);      // حسناً: Circle تنفذ جميع الدوال الافتراضية البحتة
c.printInfo();      // تستخدم printInfo() الموروثة، وتستدعي area() الخاصة بـ Circle

يجب على الفئة المشتقة (derived class) تنفيذ جميع الدوال الافتراضية البحتة (pure virtual functions) لتصبح فئة ملموسة (concrete) (قابلة للإنشاء). إذا تركت أيًا منها دون تنفيذ، فإنها تظل فئة مجردة (abstract) في حد ذاتها. تضمن هذه الآلية أن كل فئة ملموسة في التسلسل الهرمي الخاص بك توفر الوظائف المطلوبة.

challenge icon

التحدي

سهل

لنقم ببناء نظام مشغل وسائط يوضح كيف تحدد الفئات المجردة (abstract classes) العقود لأنواع الوسائط المختلفة. ستقوم بإنشاء فئة أساسية مجردة تحدد ما يجب على كل مشغل وسائط القيام به، ثم تنفيذ مشغلات ملموسة (concrete players) لمحتوى الصوت والفيديو.

ستقوم بتنظيم الكود الخاص بك عبر ثلاثة ملفات:

  • MediaPlayer.h: قم بتعريف فئة مجردة MediaPlayer تعمل كمخطط لجميع أنواع الوسائط. يجب أن تحتوي الفئة المجردة على:
    • عضو محمي (protected) باسم std::string title
    • عضو محمي (protected) باسم int duration (بالثواني)
    • منشئ (constructor) يقوم بتهيئة كلتا القيمتين
    • دالة افتراضية بحتة (pure virtual method) باسم play() — حيث سيتم تشغيل كل نوع وسائط بشكل مختلف
    • دالة افتراضية بحتة باسم getMediaType() تعيد std::string
    • دالة عادية (غير افتراضية) باسم showInfo() تطبع: <title> (<duration>s) - Type: <mediaType> — يجب أن تستدعي هذه الدالة getMediaType() للحصول على النوع
    • مدمر افتراضي (virtual destructor)
  • Players.h: قم بتنفيذ مشغلين ملموسين للوسائط يرثان من MediaPlayer:

    AudioPlayer:

    • عضو خاص (private) باسم std::string artist
    • منشئ يأخذ العنوان، والمدة، والفنان
    • تنفيذ play() لطباعة: Playing audio: <title> by <artist>
    • تنفيذ getMediaType() لتعيد "Audio"

    VideoPlayer:

    • عضو خاص (private) باسم std::string resolution
    • منشئ يأخذ العنوان، والمدة، والدقة
    • تنفيذ play() لطباعة: Playing video: <title> in <resolution>
    • تنفيذ getMediaType() لتعيد "Video"
  • main.cpp: اقرأ أربعة مدخلات (كل منها في سطر منفصل):
    1. عنوان المقطع الصوتي
    2. مدة الصوت (عدد صحيح)
    3. عنوان الفيديو
    4. مدة الفيديو (عدد صحيح)

    قم بإنشاء AudioPlayer مع الفنان "Unknown Artist" و VideoPlayer مع الدقة "1080p". قم بتخزين كليهما في مصفوفة من مؤشرات MediaPlayer*.

    قم بالمرور عبر المصفوفة واستدعاء showInfo() أولاً لكل مشغل، ثم استدعاء play(). اطبع سطراً فارغاً بين كل عنصر وسائط. قم بتنظيف الكائنات المخصصة ديناميكياً عند الانتهاء.

على سبيل المثال، مع المدخلات Summer Vibes، و 180، و Nature Documentary، و 3600:

Summer Vibes (180s) - Type: Audio
Playing audio: Summer Vibes by Unknown Artist

Nature Documentary (3600s) - Type: Video
Playing video: Nature Documentary in 1080p

لاحظ كيف تم تعريف الدالة showInfo() مرة واحدة في الفئة الأساسية المجردة ولكنها تستدعي الدالة الافتراضية البحتة getMediaType() — وهذا يوضح كيف يمكن للفئات المجردة مزج التنفيذات الملموسة مع الدوال التي يجب أن توفرها الفئات المشتقة. تذكر استخدام الكلمة المفتاحية override في جميع الدوال التي يتم تجاوزها.

ورقة مرجعية

يحتوي الفصل المجرد (abstract class) على دالة افتراضية بحتة (pure virtual function) واحدة على الأقل ولا يمكن إنشاء كائن منه مباشرة. الغرض من وجوده هو أن يتم توريثه وتحديد واجهة للفصول المشتقة.

تعريف فصل مجرد (Abstract Class)

استخدم = 0 للتصريح عن دالة افتراضية بحتة:

class Shape {
public:
    virtual double area() = 0;      // دالة افتراضية بحتة
    virtual double perimeter() = 0; // تجعل Shape مجرداً
    
    void printInfo() {              // طريقة عادية مع تنفيذ
        std::cout << "Area: " << area() << std::endl;
    }
    
    virtual ~Shape() = default;     // مدمر افتراضي
};

يمكن أن تحتوي الفصول المجردة على:

  • دوال افتراضية بحتة (بدون تنفيذ)
  • دوال افتراضية عادية (مع تنفيذ افتراضي)
  • دوال غير افتراضية

تنفيذ الفصول الملموسة (Concrete Classes)

يجب على الفصل المشتق تنفيذ جميع الدوال الافتراضية البحتة ليصبح قابلاً لإنشاء كائنات منه:

class Circle : public Shape {
    double radius;
public:
    Circle(double r) : radius(r) {}
    
    double area() override { return 3.14159 * radius * radius; }
    double perimeter() override { return 2 * 3.14159 * radius; }
};

Circle c(5.0);      // صحيح: تم تنفيذ جميع الدوال الافتراضية البحتة
c.printInfo();      // الطريقة الموروثة تستدعي area() الخاصة بـ Circle

إذا لم يقم الفصل المشتق بتنفيذ جميع الدوال الافتراضية البحتة، فإنه يظل مجرداً ولا يمكن إنشاء كائن منه.

جرّب بنفسك

#include <iostream>
#include <string>
#include "MediaPlayer.h"
#include "Players.h"

int main() {
    // قراءة المدخلات
    std::string audioTitle;
    int audioDuration;
    std::string videoTitle;
    int videoDuration;
    
    std::getline(std::cin, audioTitle);
    std::cin >> audioDuration;
    std::cin.ignore();
    std::getline(std::cin, videoTitle);
    std::cin >> videoDuration;
    
    // TODO: إنشاء AudioPlayer مع الفنان "Unknown Artist"
    
    // TODO: إنشاء VideoPlayer بدقة "1080p"
    
    // TODO: تخزين كليهما في مصفوفة من مؤشرات MediaPlayer*
    
    // TODO: التكرار عبر المصفوفة ولكل مشغل:
    //       1. استدعاء showInfo()
    //       2. استدعاء play()
    //       3. طباعة سطر فارغ بين كل عنصر وسائط
    
    // TODO: تنظيف الكائنات المخصصة ديناميكياً (dynamically allocated objects)
    
    return 0;
}
quiz iconاختبر نفسك

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

جميع دروس Object Oriented Programming