البرمجة

التعددية الشكلية في C++

تطبيق التعددية الشكلية (Polymorphism) في C++

تعتبر البرمجة الكائنية (Object-Oriented Programming) أحد أهم مفاهيم البرمجة في العصر الحالي، ومن بين أبرز المفاهيم الأساسية التي ترتكز عليها هذه البرمجة هي التعددية الشكلية (Polymorphism). يوفر التعدد في الأشكال طرقًا متعددة لتنفيذ الوظائف نفسها في سياقات مختلفة. يعد هذا المبدأ أحد العوامل الرئيسية التي تسهم في جعل البرمجيات أكثر مرونة، قابلية للتوسع، وصيانة سهلة. في هذا المقال، سنناقش مفهوم التعددية الشكلية، أنواعها، وكيفية تطبيقها باستخدام لغة البرمجة C++.

مفهوم التعددية الشكلية (Polymorphism)

التعددية الشكلية هي القدرة على استخدام دوال أو وظائف بنفس الاسم ولكن مع تنفيذات مختلفة بناءً على السياق أو نوع البيانات المستخدمة. في اللغة البرمجية C++، تعد هذه الميزة أحد الأدوات القوية في إنشاء تطبيقات مرنة وسهلة التوسع. بشكل عام، التعددية الشكلية تتيح للمطورين تعريف دالة أو طريقة بنفس الاسم في العديد من الأماكن بحيث يتم تنفيذها بطرق متعددة، وذلك استنادًا إلى النوع الفعلي للكائن الذي يتم تمريره إلى الوظيفة.

يمكن تصنيف التعددية الشكلية في C++ إلى نوعين رئيسيين:

  1. التعددية الشكلية وقت الترجمة (Compile-Time Polymorphism).

  2. التعددية الشكلية وقت التشغيل (Run-Time Polymorphism).

التعددية الشكلية وقت الترجمة (Compile-Time Polymorphism)

تحدث التعددية الشكلية وقت الترجمة عندما يتم تحديد أي دالة أو طريقة سيتم تنفيذها أثناء مرحلة الترجمة (قبل تنفيذ البرنامج). من أبرز الأمثلة على هذه النوعية من التعددية الشكلية هي:

التحميل الزائد للدوال (Function Overloading)

التحميل الزائد هو العملية التي يتم فيها تعريف دوال بنفس الاسم ولكن مع معلمات (Parameters) مختلفة. يتم اختيار الدالة التي سيتم استدعاؤها بناءً على عدد أو نوع المعاملات المدخلة.

مثال:

cpp
#include using namespace std; // دالة لإضافة عددين int add(int a, int b) { return a + b; } // دالة لإضافة ثلاثة أعداد int add(int a, int b, int c) { return a + b + c; } int main() { cout << "نتيجة إضافة عددين: " << add(3, 5) << endl; // استدعاء دالة بإضافة عددين cout << "نتيجة إضافة ثلاثة أعداد: " << add(3, 5, 7) << endl; // استدعاء دالة بإضافة ثلاثة أعداد return 0; }

في المثال أعلاه، تم تعريف دالتين بنفس الاسم “add”، ولكن الأولى تأخذ معاملين والثانية تأخذ ثلاثة معطيات. يقوم المترجم بتحديد الدالة المناسبة بناءً على عدد المعاملات المدخلة في كل حالة.

التحميل الزائد للمشغل (Operator Overloading)

التحميل الزائد للمشغل يعني أن المشغلات (مثل +, -, *, إلخ) يمكن أن يتم تعديل سلوكها للعمل مع أنواع البيانات المخصصة (مثل الكائنات الخاصة بالفئات).

مثال:

cpp
#include using namespace std; class Complex { private: float real; float imag; public: Complex(float r, float i) : real(r), imag(i) {} // تحميل زائد للمشغل + Complex operator + (const Complex& obj) { return Complex(real + obj.real, imag + obj.imag); } void display() { cout << real << " + " << imag << "i" << endl; } }; int main() { Complex c1(3.5, 2.5), c2(1.5, 4.5); Complex c3 = c1 + c2; // استخدام المشغل + c3.display(); return 0; }

في المثال أعلاه، تم تعديل سلوك المشغل + ليتمكن من إضافة كائنات من نوع Complex بدلاً من الأعداد البسيطة.

التعددية الشكلية وقت التشغيل (Run-Time Polymorphism)

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

التوريث (Inheritance) و الدوال الافتراضية (Virtual Functions)

عندما يتم إنشاء فئة مشتقة (derived class) من فئة أساسية (base class)، يمكن للفئة المشتقة أن تغير (أو تطور) طريقة تنفيذ الدوال التي تم تعريفها في الفئة الأساسية. إذا كانت الدالة في الفئة الأساسية مصنفة كـ virtual، فإن التنفيذ الفعلي للدالة في وقت التشغيل يعتمد على نوع الكائن الفعلي وليس على نوع المؤشر أو المرجع.

مثال:

cpp
#include using namespace std; class Shape { public: virtual void draw() { // دالة افتراضية cout << "الرسم من الفئة الأساسية" << endl; } }; class Circle : public Shape { public: void draw() override { // إعادة تعريف الدالة cout << "رسم دائرة" << endl; } }; class Square : public Shape { public: void draw() override { // إعادة تعريف الدالة cout << "رسم مربع" << endl; } }; int main() { Shape* shape; Circle circle; Square square; shape = &circle; shape->draw(); // سيتم استدعاء دالة "draw" الخاصة بـ "Circle" shape = □ shape->draw(); // سيتم استدعاء دالة "draw" الخاصة بـ "Square" return 0; }

في المثال أعلاه، تم تعريف دالة draw كدالة افتراضية في الفئة الأساسية Shape، وتمت إعادة تعريفها في الفئات المشتقة Circle و Square. عند استدعاء draw() باستخدام المؤشر shape، سيتم استدعاء التنفيذ الفعلي للوظيفة بناءً على نوع الكائن الذي يتم تمريره.

الفائدة من التعددية الشكلية في C++

  1. التوسعة السهلة: يمكن للمطورين إضافة فئات جديدة بدون الحاجة إلى تعديل الكود السابق. فبمجرد أن يتم تعريف دالة افتراضية في الفئة الأساسية، يمكن للفئات المشتقة تقديم سلوكها الخاص لهذه الدالة.

  2. التنفيذ الديناميكي: يمكننا كتابة برامج ديناميكية بحيث يتم تحديد نوع التنفيذ بناءً على نوع الكائن في وقت التشغيل.

  3. إخفاء التفاصيل الداخلية: باستخدام التعددية الشكلية، يمكن إخفاء التفاصيل المعقدة للمستخدم النهائي. يمكن للمطورين استخدام واجهات بسيطة تتعامل مع كائنات من فئات متعددة.

خاتمة

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