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

إعادة تعريف الدوال (Method Overriding)

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

يحدث تجاوز الدوال (Method overriding) عندما توفر فئة مشتقة (derived class) تنفيذًا خاصًا بها لدالة موجودة بالفعل في الفئة الأساسية (base class). يتيح ذلك للفئات المشتقة تخصيص السلوك الموروث مع الحفاظ على نفس توقيع الدالة (method signature).

لتجاوز دالة (method)، ما عليك سوى تعريف دالة في الفئة المشتقة بنفس الاسم ونوع الإرجاع والمعاملات تماماً:

class Animal {
public:
    void speak() {
        std::cout << "Some sound" << std::endl;
    }
};

class Dog : public Animal {
public:
    void speak() {
        std::cout << "Woof!" << std::endl;
    }
};

Dog d;
d.speak();  // المخرجات: Woof!

إصدار الفئة المشتقة يخفي إصدار الفئة الأساسية. ومع ذلك، لا يزال بإمكانك الوصول إلى دالة الفئة الأساسية باستخدام عامل دقة النطاق (scope resolution operator):

class Dog : public Animal {
public:
    void speak() {
        Animal::speak();  // استدعاء إصدار الفئة الأساسية
        std::cout << "Woof!" << std::endl;
    }
};

هناك قيد مهم في هذا الشكل الأساسي من التجاوز (overriding). فعندما تصل إلى كائن مشتق عبر مؤشر أو مرجع للفئة الأساسية، يتم استدعاء نسخة الفئة الأساسية بدلاً من ذلك:

Dog d;
Animal* ptr = &d;
ptr->speak();  // المخرجات: Some sound (ليس Woof!)

يحدث هذا لأن المترجم يحدد الطريقة التي سيتم استدعاؤها بناءً على نوع المؤشر، وليس نوع الكائن الفعلي. لتحقيق تعدد الأشكال الحقيقي في وقت التشغيل (runtime polymorphism) حيث يتم استدعاء الطريقة الصحيحة بناءً على الكائن الفعلي، ستحتاج إلى الدوال الافتراضية (virtual functions)، والتي سنغطيها في الدرس القادم.

challenge icon

التحدي

سهل

لنقم ببناء نظام إشعارات يوضح عملية تجاوز الدوال (method overriding) بشكل عملي. ستقوم بإنشاء فئة أساسية Notifier وفئتين متخصصتين للإشعارات تقومان بتخصيص كيفية تسليم الرسائل.

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

  • Notifier.h: قم بتعريف فئة أساسية Notifier تحتوي على:
    • عضو محمي (protected) باسم std::string recipient
    • منشئ (constructor) يأخذ اسم المستلم ويقوم بتخزينه
    • دالة عامة (public) باسم send(const std::string& message) تقوم بطباعة: Sending to <recipient>: <message>
  • EmailNotifier.h: قم بتعريف فئة EmailNotifier ترث علنًا (publicly inherits) من Notifier:
    • منشئ يأخذ المستلم ويمرره إلى الفئة الأساسية
    • تجاوز (Override) لدالة send() لاستدعاء نسخة الفئة الأساسية أولاً باستخدام Notifier::send()، ثم طباعة: [Email sent successfully]
  • main.cpp: اقرأ مدخلين (كل منهما في سطر منفصل):
    1. اسم المستلم (string)
    2. محتوى الرسالة (string)

    قم بتعريف فئة SMSNotifier مباشرة في ملف main.cpp ترث علنًا من Notifier:

    • منشئ يأخذ المستلم ويمرره إلى الفئة الأساسية
    • تجاوز لدالة send() لطباعة: SMS to <recipient>: <message> (مع استبدال سلوك الفئة الأساسية تمامًا دون استدعائها)

    قم بإنشاء كائن واحد من كل نوع من أنواع الإشعارات (الفئة الأساسية Notifier، و EmailNotifier، و SMSNotifier) باستخدام نفس المستلم. استدعِ دالة send() لكل منها مع رسالة الإدخال، مع طباعة سطر فاصل بين كل منها:

    --- Base Notifier ---
    <output>
    --- Email Notifier ---
    <output>
    --- SMS Notifier ---
    <output>

على سبيل المثال، مع المدخلات Alice و Hello there!:

--- Base Notifier ---
Sending to Alice: Hello there!
--- Email Notifier ---
Sending to Alice: Hello there!
[Email sent successfully]
--- SMS Notifier ---
SMS to Alice: Hello there!

لاحظ كيف تقوم EmailNotifier بتوسيع سلوك الفئة الأساسية عن طريق استدعاء دالة الأب أولاً، بينما تقوم SMSNotifier باستبدالها تمامًا بتنفيذها الخاص. لا تنسَ استخدام حراس الرأس (header guards) وتضمين الرؤوس المناسبة في كل ملف.

ورقة مرجعية

يسمح تجاوز الدوال (Method overriding) للفئة المشتقة بتقديم تنفيذ خاص بها لدالة من الفئة الأساسية، مع الحفاظ على نفس توقيع الدالة (الاسم، نوع الإرجاع، والمعاملات).

الصيغة الأساسية للتجاوز:

class Animal {
public:
    void speak() {
        std::cout << "Some sound" << std::endl;
    }
};

class Dog : public Animal {
public:
    void speak() {
        std::cout << "Woof!" << std::endl;
    }
};

Dog d;
d.speak();  // Output: Woof!

تقوم دالة الفئة المشتقة بإخفاء نسخة الفئة الأساسية. لاستدعاء دالة الفئة الأساسية من الفئة المشتقة، استخدم عامل دقة النطاق (scope resolution operator):

class Dog : public Animal {
public:
    void speak() {
        Animal::speak();  // استدعاء نسخة الفئة الأساسية
        std::cout << "Woof!" << std::endl;
    }
};

قيد مهم: عند الوصول إلى كائن مشتق من خلال مؤشر أو مرجع للفئة الأساسية، يتم استدعاء نسخة الفئة الأساسية:

Dog d;
Animal* ptr = &d;
ptr->speak();  // Output: Some sound (not Woof!)

هذا لأن المترجم يحدد الدالة التي سيتم استدعاؤها بناءً على نوع المؤشر، وليس نوع الكائن الفعلي. لتحقيق تعدد الأشكال الحقيقي في وقت التشغيل (runtime polymorphism)، استخدم الدوال الافتراضية (virtual functions).

جرّب بنفسك

#include <iostream>
#include <string>
#include "Notifier.h"
#include "EmailNotifier.h"

using namespace std;

// TODO: قم بتعريف فئة SMSNotifier هنا بحيث ترث بشكل علني (publicly) من Notifier
// - منشئ (Constructor) يمرر recipient إلى الفئة الأساسية (base class)
// - قم بعمل override للدالة send() لطباعة: SMS to <recipient>: <message>
//   (استبدل سلوك الفئة الأساسية تماماً، ولا تستدعي send() الخاصة بالأب)

int main() {
    string recipient;
    string message;
    
    getline(cin, recipient);
    getline(cin, message);
    
    // TODO: قم بإنشاء كائن Notifier مع recipient
    // اطبع "--- Base Notifier ---"
    // استدعِ send() مع message
    
    // TODO: قم بإنشاء كائن EmailNotifier مع recipient
    // اطبع "--- Email Notifier ---"
    // استدعِ send() مع message
    
    // TODO: قم بإنشاء كائن SMSNotifier مع recipient
    // اطبع "--- SMS Notifier ---"
    // استدعِ send() مع message
    
    return 0;
}
quiz iconاختبر نفسك

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

جميع دروس Object Oriented Programming