Expand Your Oop Knowledge موضوع متواضع عن virtual functions

الناقل : elmasry | الكاتب الأصلى : Robatic | المصدر : www.arabteam2000-forum.com

السلام عليكم ورحمة الله وبركاته
كيف الحال يا أخواني الأعزاء

يا اخواني إن شاء الله في هذا الموضوع راح اتكلم ( إذا سمحتولي ) عن الـ OOP و شوية عن الـ Inheritance
وكلنا نعرف عنها، لكن راح اتطرق إلى الـ virtual functions بالأخص
بس قبل كل حاجة خلوني اعطيكم مقدمة بسيطة عن الـ Pointers to Objects

لو مثلا كان عندنا كلاس بسيط بإسم MyClass ، عشان نعمل منه كائن نكتب التالي

MyClass someObject


إلى الآن مافي شيء جديد، طيب لو نريد نعمل كائن ولكن مش أي كائن بل مؤشر، نكتب التالي
MyClass* obPtr = new MyClass()


إلى الآن الي عنده خلفية عن الـ Java او الـ C# ما عنده اي مشكلة

طيب نفرض إنه عندنا كلاسين Base و Derived حيث انه الثاني قام بوراثة الأول، هل العبارة التالية صحيحه ؟؟؟
Base* ptr = new Derived()


نعم ( ممكن البعض يتفاجأ ) ولكن خلونا نوضح اكثر، افرض الكلاسين الي تم ذكرهم فوق معرفين بالطريقة التالي :
class Base
{
public:
        void function() { cout << "This is from Base Class\n"; }
};

class Derived : public Base
{
public:
        void function() { cout << "This is from Derived Class\n"; }
};

ودالة الـ main معرفة على النحو التالي :
void main()
{
        Base *b = new Base();
        b->function();

        Derived *d = new Derived();
        d->function();
}


طبعا الـ output راح يكون كالتالي ( اوه كم مرة كتبت كلمة "التالي" ؟؟ :S )

This is from Base Class
This is from Derived Class


طيب لو أعدنا تعريف الـ main كما يلي ( لازم من التغيير؟؟؟!!!! ):
void main()
{
        Base *b = new Derived();
        b->function();
}


ماذا ستكون المخرجات ؟؟؟؟؟
This is from Base


همممممممم ؟؟ محير !!!!!
بكل بساطة على الرغم من إنه تم تعديل "Overload" للدالة function ولكن ما زالت تستخدم النسخة الموجود في Base Class بدلا من
النسخة الموجوده في Derived Class
لحل هذه المشكلة نقوم بتعريف الدالة function والموجوده في الـ Base على أنها virtual كالتالي

class Base
{
public:
        virtual void function() { cout << "This is from Base Class\n"; }
};


ثم نقوم بتنفيذ البرنامج.

طيب إش الفائدة من هذا الكلام أو كيف ممكن نسخر هذا الكلام لنا ؟؟؟؟
اقول : إذاذ كان عندنا كلاس A وكان في كلاس ثاني B والكلاس B ورث من الكلاس A فإن الكلاس A هو B والعكس غير صحيح
يعني نقدر نستخدم مؤشر من نوع A يأشر على B

خلونا نعدل في الكود تبع الـ Base والـ Derived كالتالي ونضيف دالة خارجية نرسلها Pointer من نوع Base وتقوم بتنفيذ الدالة function
ونشوف كيف تطلع النتيجة

#include <iostream>

using namespace std;

class Base
{
public:
        virtual void function() { cout << "This is from Base Class\n"; }
};

class Derived : public Base
{
public:
        void function() { cout << "This is from Derived Class\n"; }
};


void test(Base* anything) {anything->function();}


void main()
{
        Base* p = new Base();
        test(p);

        p = new Derived();
        test(p);
}


لو تلاحظوا ناتج المخرجات إنه تم تنفيذ الدالة على حسب مهي معرفة في كل كلاس مع إنه الدالة test ما تستقبل إلا pointer من نوع Base
بمعنى آخر إحنا قدرنا نعمم الدالة test على كل من Base & Derived.

كيف يفيدنا هذا التكتيك ؟؟
خذ السناريو التالي :
تخيل نفسك شغال في بنك كمبرمج، وقمت بكتابة برنامج الصراف الآلي ATM بحيث انه يستقبل حساب توفير وحساب جاري
وتحليلك كان كالتالي :
كتابة كلاس خاص بحساب جاري RegularAccount
كتابة كلاس خاص بحساب توفير SavingAccount
والفرق بينهم انه في الـ RegularAccount كان في دالة إسمها withdrawl وهي مسؤولة عن عملية السحب النقدي وتم عمل overload
لنفس الدالة في الـ SavingAccount بحيث إنه تختلف في حساب معدل الفائدة لكل من الحسابين
فبدون الـ virtual function راح يكون البرنامج تقريبا كالتالي ( نظريا):
تحديد نوع الكائن إذا كان RegularAccount أو SavingAccount
بحيث يتم عمل كائن جديد من نفس النوع ثم قم بتنفيذ الدالة withdrawl

طيب لو طلب منك المدير إضافة 1000 نوع حساب جديد كيف الحل ؟؟؟!!!!
على نفس المنطق المذكور أعلاه راح تقوم بكتابة 1000 IF و 1000 سطر لإنشاء كائن و 1000 إستدعاء للدالة withdrawl

هنا يجيء دور الـ Pure Abstract Class و الـ Virtual Functions
حيث إنه تقوم بعمل Abstract Class بإسم مثلا Account وفيه الدالة withdrawl معرفة على إنها virtual وبعد كذا قوم بتعريف كلاس لكل حساب ولكن قم بتوريثة للـ Account وقم بتغيير الدالة withdrawl على حسب ما يتناسب لكل نوع حساب
وبعد ذلك قوم بكتابة دالة عامة تقوم بإستدعاء الدالة withdrawl ولكن عن طريق pointer من نوع Account يتم إرساله لها على النحو التالي

void doWithdrawl(Account* ob) { ob->withdrawl(); }


بهذه الطريقة قدرنا نضمن إنه الدالة doWithdrawl راح تقوم بعملية السحب النقدي بشكل صحيح لكل حساب بغض النظر عن نوع الحساب
وهذا ما يسمى بالـ Generic Programming ونفس الأسلوب ينطبق على الـ Java & CSharp


إن شاء الله ما اكون لخبطكم وإن شاء الله يكون هذا الدرس مفيد وأتمنى لكم التوفيق

اخوكم Robatic