البرمجة الكينونية (OOP) بتقوم على 4 أعمدة:
- التغليف (Encapsulation)
- الوراثة (Inheritance)
- التجريد (Abstraction)
- تعدد الأشكال (Polymorphism) ← موضوعنا اليوم
كلمة Polymorphism أصلها يوناني، مكونة من:
- Poly = متعدد
- Morph = شكل
يعني: “تعدد الأشكال” —
نفس الاسم، لكن سلوك مختلف حسب السياق.
🎯 ما الفكرة ببساطة؟
الـ Polymorphism يسمح لك تستخدم نفس الدالة بنفس الاسم في عدة كائنات،
لكن كل كائن ينفذها بطريقته الخاصة.
مثلاً:
- دالة
speak()في الفئةAnimal - الكلب ينبح، القطة تموء، الطائر يغرد.
كلهم “يتكلمون”، لكن بشكل مختلف.
⚙️ أنواع تعدد الأشكال في C++
في C++ عندنا نوعين رئيسيين من الـ Polymorphism:
| النوع | الاسم الكامل | متى يُحدد السلوك؟ | الأمثلة |
|---|---|---|---|
| Compile-time Polymorphism | تعدد الأشكال أثناء الترجمة | أثناء الترجمة (compile) | Overloading |
| Runtime Polymorphism | تعدد الأشكال أثناء التشغيل | وقت التنفيذ | Overriding + Virtual Functions |
خلينا نمشي فيهم بالترتيب ونفهم العمق 👇
🧩 النوع الأول: Compile-Time Polymorphism
ويتحقق عن طريق Function Overloading أو Operator Overloading.
(رح نركز الآن على Overloading لأنها الأساس.)
🔹 Function Overloading (تعدد تعريف الدوال)
يعني عندك أكثر من دالة بنفس الاسم داخل نفس الفئة،
لكن تختلف بعدد أو نوع المعاملات (parameters).
📘 مثال بسيط:
#include <iostream>
using namespace std;
class Math {
public:
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
int add(int a, int b, int c) {
return a + b + c;
}
};
int main() {
Math m;
cout << m.add(2, 3) << endl;
cout << m.add(1.5, 2.3) << endl;
cout << m.add(1, 2, 3) << endl;
}
📤 النتيجة:
5
3.8
6
كل الدوال اسمها add()،
لكن المترجم (compiler) عرف أي وحدة يستدعي حسب نوع وعدد المعاملات.
🧩 النوع الثاني: Runtime Polymorphism
وهون يبدأ السحر الحقيقي في C++.
النوع اللي يعتمد على الوراثة (Inheritance) و الدوال الافتراضية (Virtual Functions).
وهو اللي بنسميه Function Overriding.
⚡ ما هو Function Overriding؟
يعني:
- الفئة الابنة (Derived Class) تعيد تعريف دالة موجودة في الفئة الأم (Base Class).
- وقت التشغيل، C++ تختار أي دالة تُنفّذ حسب نوع الكائن الحقيقي.
📘 مثال بسيط بدون virtual:
#include <iostream>
using namespace std;
class Animal {
public:
void sound() {
cout << "صوت حيوان عام." << endl;
}
};
class Dog : public Animal {
public:
void sound() {
cout << "ينبح!" << endl;
}
};
int main() {
Animal* a;
Dog d;
a = &d;
a->sound(); // أي دالة ستُستدعى؟
}
📤 النتيجة:
صوت حيوان عام.
😮 ليش؟
رغم أن a تشير إلى كائن من نوع Dog،
إلا أن المترجم استخدم الدالة التابعة للنوع الظاهري (Animal)،
وليس النوع الحقيقي (Dog).
هون بنحتاج الكلمة السحرية: virtual 👇
🧠 Virtual Functions — الدوال الافتراضية
هي المفتاح اللي بيخلّي C++ تختار الدالة الصحيحة وقت التشغيل وليس وقت الترجمة.
أي دالة تعلن عنها كـ virtual في الفئة الأم، يمكن “إعادة تعريفها” في الفئة الابنة.
📘 نفس المثال لكن باستخدام virtual:
#include <iostream>
using namespace std;
class Animal {
public:
virtual void sound() {
cout << "صوت حيوان عام." << endl;
}
};
class Dog : public Animal {
public:
void sound() override {
cout << "ينبح!" << endl;
}
};
int main() {
Animal* a;
Dog d;
a = &d;
a->sound(); // 🔥 الآن سيختار دالة Dog
}
📤 النتيجة:
ينبح!
💡 السبب:
عند استخدام virtual، يقوم C++ بإنشاء جدول داخلي (vtable) لتخزين عناوين الدوال الافتراضية.
وفي وقت التنفيذ، يختار الدالة بناءً على نوع الكائن الفعلي.
⚙️ الفرق بين Overloading و Overriding
| المقارنة | Function Overloading | Function Overriding |
|---|---|---|
| مكان التعريف | في نفس الفئة | بين فئتين (وراثة) |
| التوقيت | وقت الترجمة | وقت التشغيل |
| الاعتماد | عدد أو نوع المعاملات | نفس الاسم والتوقيع |
| الكلمة المفتاحية | لا تحتاج | تحتاج virtual |
| الهدف | نفس الفئة – تنويع الوظيفة | تغيير السلوك في الفئات الموروثة |
🧱 مثال تطبيقي أوسع
#include <iostream>
using namespace std;
class Employee {
public:
virtual void role() {
cout << "موظف عام في الشركة.\n";
}
};
class Developer : public Employee {
public:
void role() override {
cout << "مطوّر يكتب الكود.\n";
}
};
class Designer : public Employee {
public:
void role() override {
cout << "مصمم يصمم الواجهات.\n";
}
};
int main() {
Employee* e1 = new Developer();
Employee* e2 = new Designer();
e1->role(); // مطوّر
e2->role(); // مصمم
delete e1;
delete e2;
}
📤 النتيجة:
مطوّر يكتب الكود.
مصمم يصمم الواجهات.
رغم أن النوع الظاهري هو Employee*،
لكن البرنامج اختار الدالة المناسبة بناءً على الكائن الحقيقي وقت التشغيل.
🧩 استخدام الـ override
الكلمة المفتاحية override اختيارية لكنها مفيدة.
تستخدم لتوضيح أنك تعيد تعريف دالة من الفئة الأم،
وتعطي تحذير لو أخطأت في التوقيع أو الاسم.
🧱 المدمّر الافتراضي (Virtual Destructor)
لو عندك فئة فيها مدمّر Destructor وتستخدم الوراثة،
لازم يكون افتراضيًا لتجنب تسرب الذاكرة.
class Base {
public:
virtual ~Base() {
cout << "تم حذف Base\n";
}
};
class Derived : public Base {
public:
~Derived() {
cout << "تم حذف Derived\n";
}
};
int main() {
Base* b = new Derived();
delete b; // يستدعي كلا المدمّرين بترتيب صحيح
}
📤 النتيجة:
تم حذف Derived
تم حذف Base
⚠️ ملاحظات مهمة
- لا يمكن أن تكون الدوال الافتراضية constructors.
لأنها تُستخدم لتخصيص السلوك بعد الإنشاء، وليس أثناءه. - يمكن أن تكون virtual دوال عادية أو protected أو حتى pure virtual.
- تستهلك القليل من الذاكرة الإضافية (بسبب vtable)،
لكنها تضيف مرونة هائلة في التصميم.
🧩 الدوال الافتراضية المجردة (Pure Virtual Functions)
هي دوال افتراضية بدون تنفيذ داخل الفئة الأم،
تجعل الفئة “مجردة” (Abstract Class) لا يمكن إنشاء كائن منها مباشرة.
class Shape {
public:
virtual void draw() = 0; // pure virtual function
};
أي فئة ترث منها يجب أن تعرّف الدالة draw() بنفسها.
📘 مثال عملي:
#include <iostream>
using namespace std;
class Shape {
public:
virtual void draw() = 0; // دالة مجردة
};
class Circle : public Shape {
public:
void draw() override {
cout << "رسم دائرة." << endl;
}
};
class Square : public Shape {
public:
void draw() override {
cout << "رسم مربع." << endl;
}
};
int main() {
Shape* s1 = new Circle();
Shape* s2 = new Square();
s1->draw();
s2->draw();
delete s1;
delete s2;
}
📤 النتيجة:
رسم دائرة.
رسم مربع.
🔹 الفئة Shape مجردة — لا يمكن إنشاء كائن منها مباشرة.
🔹 كل فئة فرعية تنفّذ draw() بطريقتها الخاصة.
🧭 الربط مع مفاهيم OOP الأخرى
| المفهوم | دوره بجانب Polymorphism |
|---|---|
| Encapsulation | يحمي البيانات داخل الكائنات. |
| Inheritance | يسمح بنقل الخصائص والسلوك. |
| Polymorphism | يجعل نفس الواجهة تتصرف بطرق مختلفة. |
| Abstraction | يخفي التعقيد ويوضح فقط ما تحتاجه. |
الـ Polymorphism هو المرحلة اللي يتحول فيها الكود من “جماد” إلى “سلوك متغير”.
🧱 مشروع صغير لتوضيح الفكرة بالكامل
#include <iostream>
#include <vector>
using namespace std;
// فئة مجردة عامة
class Employee {
public:
virtual void work() = 0;
virtual ~Employee() {}
};
class Developer : public Employee {
public:
void work() override {
cout << "المطور يكتب الكود." << endl;
}
};
class Designer : public Employee {
public:
void work() override {
cout << "المصمم يرسم الواجهات." << endl;
}
};
class Manager : public Employee {
public:
void work() override {
cout << "المدير يدير الفريق." << endl;
}
};
int main() {
vector<Employee*> team;
team.push_back(new Developer());
team.push_back(new Designer());
team.push_back(new Manager());
for (auto e : team) {
e->work();
}
for (auto e : team) delete e;
}
📤 النتيجة:
المطور يكتب الكود.
المصمم يرسم الواجهات.
المدير يدير الفريق.
لاحظ كيف نفس الدالة work() أعطت سلوك مختلف حسب الكائن —
وهذا هو الـ Polymorphism في أنقى صوره.
🌍 روابط خارجية موثوقة
- 🔗 cppreference – Virtual Functions
- 🔗 Programiz – Function Overriding in C++
- 🔗 W3Schools – C++ Polymorphism
🧩 الخلاصة النهائية
🔹 Function Overloading = تعدد أشكال في وقت الترجمة
🔹 Function Overriding = تعدد أشكال في وقت التشغيل
🔹 Virtual Functions = قلب الـ Polymorphism الحقيقي
🔹 Pure Virtual Functions = تجعل الفئة مجردة (أساس لواجهات قوية)
الـ Polymorphism مش مجرد ميزة… هو فلسفة.
فلسفة بتخلي الكود مرن، سهل التطوير، ويقدر يتصرف بناءً على نوع الكائن اللي يتعامل معه.
هو اللي بيحوّل البرمجة من “مجموعة أوامر” إلى “كائنات تفكر وتقرر”.
اكتشاف المزيد من كود التطور
اشترك للحصول على أحدث التدوينات المرسلة إلى بريدك الإلكتروني.


