C++ Overriding

مفهوم إعادة التعريف

في الدرس السابق, شاهدت كيف أنه عندما يرث الكلاس من كلاس آخر فإنه يرث المتغيرات و الدوال الموجودة فيه.

إعادة التعريف أو التعريف من جديد ( Overriding ) تعني تعريف نفس الدالة التي ورثها الكلاس الإبن من الكلاس الأب من جديد, و هذه الدالة الجديدة تكون مشابهة للدالة الموروثة من حيث الشكل فقط, أي لها نفس الإسم و النوع و عدد الباراميترات, لكن محتواها مختلف بهدف أن يكون متناسب أكثر مع الكلاس الإبن.

الهدف الحقيقي من إعادة التعريف أو التعريف من جديد هو إتاحة الفرصة للكلاس الإبن ليعرّف الدوال حسب حاجته.
في دروس متقدمة سنرث من كلاسات جاهزة في C++, و نفعل Override للدوال الموجودة فيها لكي تناسب التطبيقات التي سنقوم ببنائها.


شروط إعادة تعريف الدوال الموروثة

  • يجب أن يكون الـ Modifier المستخدم للدالة الجديدة هو نفسه المستخدم للدالة القديمة, و يجب أن يكون نوعه public أو protected.
  • عدد و نوع باراميترات الدالة الجديدة يجب أن يطابق عدد و نوع باراميترات الدالة القديمة.
  • نوع الإرجاع للدالة الجديدة يجب أن يكون نفس نوع الإرجاع للدالة القديمة.
  • الدالة المعرفة كـ private لا يمكن أن نفعل لها Override, لأن كلمة private تمنع إمكانية الوصول المباشر للدالة من الكلاس الإبن.
  • الدالة المعرفة كـ final لا يمكن أن نفعل لها Override, لأن كلمة final تمنع تغير محتوى الدالة بعد تعريفها.
  • الدالة المعرفة كـ static لا يمكن أن نفعل لها Override و لكن يمكن تعريفها من جديد في أي مكان, لأن كلمة static تجعل الدالة مشتركة بين جميع الكلاسات.
  • لا يمكن أن نفعل Override للكونستركتور.

مثال يوضح فائدة إعادة تعريف الدوال

في المثال التالي قمنا بتعريف كلاس إسمه Country يعتبر الكلاس الأساسي لأي بلد.
في هذا الكلاس قمنا بتعريف دالة إسمها language() فكرتها طباعة لغة البلد و جعلناها تطبع اللغة الإنجليزية بشكل إفتراضي كلغة أي بلد.

بعدها قمنا بتعريف ثلاث كلاسات تمثل ثلاث بلدان مختلفة (أستراليا, لبنان, إسبانيا) لذلك جعلناها ترث من الكلاس Country الذي يعتبر أساس أي بلد.
إذاً الكلاسات الثلاثة سترث الدالة language().

هنا الفكرة التي نريد إيصالها هي أن أي كلاس يرث من Country قد يضطر إلى تعريف الدالة language() من جديد حتى تناسبه.
فمثلاً لبنان لغته هي العربية, و إسبانيا لغتها هي الإسبانية.

بعد إنشاء هذه الكلاسات, قمنا بإنشاء كائنات منها و استدعاء الدالة language() من كل واحد فيهم لطباعة اللغة التي يتكلمها الناس فبه.

مثال

main.cpp
#include <iostream>

using namespace std;

// الذي يعتبر الكلاس الأساسي لأي دولة في العالم, إذاً يجب أن يرثه كل كلاس يمثل دولة Country هنا قمنا بتعريف الكلاس
class Country {

	// هنا قمنا بتعريف دالة تقوم بطباعة لغة البلد و جعلناها تطبع اللغة الإنجليزية كاللغة الإفتراضية لأي بلد
    public:
        void language()
        {
            cout << "English \n";
        }

};


// Country و يرث من الكلاس Australia هنا قمنا بتعريف كلاس يمثل دولة أستراليا و إسمه
class Australia : public Country {
	
	// من جديد لأن اللغة الإنجليزية هي لغة أستراليا أصلاً language() هنا لا داعي لتعريف الدالة

};


// Country و يرث من الكلاس Lebanon هنا قمنا بتعريف كلاس يمثل دولة لبنان و إسمه
class Lebanon : public Country {

	// من جديد لأن اللغة الإنجليزية ليست لغة لبنان language() هنا يجب تعريف الدالة
    public:
        void language()
        {
            cout << "Arabic \n";
        }

};


// Country و يرث من الكلاس Spain هنا قمنا بتعريف كلاس يمثل دولة إسبانيا و إسمه
class Spain : public Country {

	// من جديد لأن اللغة الإنجليزية ليست لغة إسبانيا language() هنا يجب تعريف الدالة
    public:
        void language()
        {
            cout << "Spanish \n";
        }

};


// main() هنا قمنا بتعريف الدالة
int main()
{
    // هنا قمنا بإنشاء كائنات من البلدان الثلاثة
    Australia au;
    Lebanon lb;
    Spain sp;

    // لعرض لغة كل بلد language() هنا قمنا باستدعاء الدالة
    au.language();
    lb.language();
    sp.language();

    return 0;
}
		

سنحصل على النتيجة التالية عند التشغيل.

English
Arabic
Spanish 
		


أنت الآن فهمت لما قد تحتاج أن تفعل Override.
سنقوم الآن بشرح طريقة عمل المترجم عندما فعلنا Override للدالة language().

إذا عدت للكلاس Lebanon, ستجد أننا فعلنا Override للدالة language().
إذاً هنا أصبح الكلاس Lebanon فعلياً يملك دالتين إٍسمهما language(), الأولى هي التي ورثها من الكلاس Country و الثانية هي التي قمنا بتعريفها فيه.


بما أن الكلاس Lebanon يملك دالتين لهما نفس الإسم, النوع و عدد البارامتيرات, كيف عرف المترجم أي دالة يختار؟

عندما قمنا بتعريف الدالة language() من جديد في الكلاس Lebanon (أي الكلاس الإبن) قام المترجم بإخفاء الدالة الأصلية التي ورثها من الكلاس Country (أي الكلاس الأب) و أظهر الدالة الجديدة فقط.


ملاحظة

داخل الكلاس Lebanon, لا يزال بإمكانك إستخدام الدالة language() الموجودة في الكلاس Country و التي قام المترجم بإخفائها عندما قمنا بتعريفها من جديد و هذا ما سنتعرف عليه في المثال التالي.

مفاهيم مهمة حول إعادة تعريف الدوال


المفهوم الأول

لاحظنا قبل قليل أنه عند إعادة تعريف الدالة التي ورثها الكلاس الإبن من الكلاس الأب أن المترجم يقوم بإخفاء الدالة التي ورثها الكلاس الإبن و إظهار التي تم تعريفها فيه, و لكننا قلنا بأن الدالة الموجودة في الكلاس الأب لا يزال بإمكان الكلاس الإبن أن يصل إليها حتى و لو كان قد تم إخفاؤها و هذا ما سنتعلمه من هذا المثال.
هنا وضعنا مثال حول كيفية كيفية استدعاء الدالة الموجودة في الكلاس الأب.

شاهد المثال »



المفهوم الثاني

إذا كنت تريد كتابة الكود بشكل يكون سهل التعامل معه و مراجعته لاحقاً فمن الأفضل إضافة الكلمة virtual لأي دالة تريد من الكلاس الذي يرثها أن يقوم بإعادة تعريفها حتى تتناسب معه, و في الكلاس الذي يرثها من الأفضل أيضاً أن تضيف الكلمة override للدالة التي تعيد تعريفها من جديد في الكلاس الإبن.

شاهد المثال »



المفهوم الثالث

إذا كنت تريد تعريف شكل الدالة ( Prototype ) فقط في الكلاس و إجبار أي كلاس يرثها على أن يقوم بتعريفها فيمكنك إستخدام الكلمة virtual لتحقيق ذلك.
أسلوب كتابة الكود المتبع في هذا المثال يقال له التجريد ( Abstraction ).

شاهد المثال »



المفهوم الرابع

إذا كنت تريد منع الكلاس الإبن من إعادة تعريف الدالة التي ورثها من الكلاس فيمكنك جعل نوع الدالة virtual final حتى أنه يمكنك جعل نوع الكلاس بأكمله final لا يمكن إعادة تعريف أي دالة موجودة فيه بهدف أن يتم استخدام الدوال الموجودة فيه كما هي.

شاهد المثال »

الدورات

أدوات مساعدة

أقسام الموقع

دورات
مقالات كتب مشاريع أسئلة