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()
من كل واحد فيهم لطباعة اللغة التي يتكلمها الناس فبه.
مثال
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
لا يمكن إعادة تعريف أي دالة موجودة فيه بهدف أن يتم استخدام الدوال الموجودة فيه كما هي.