التكرار متعدد الأشكال
جزء من قسم Object Oriented Programming في رحلة C على Coddy — الدرس 42 من 61.
لقد تعلمت كيفية إنشاء الواجهات (interfaces) وتنفيذها باستخدام دوال ملموسة (concrete functions) مختلفة. وتأتي الفائدة الحقيقية عندما يمكنك التعامل مع مجموعة من الكائنات المختلفة بشكل موحد — عبر التكرار خلالها واستدعاء نفس مؤشر الدالة (function pointer) لكل منها، بغض النظر عن السلوك المحدد المبرمج بداخلها.
تخيل أن لديك مصفوفة من هياكل Action، لكل منها دالة execute الخاصة بها والمخصصة لها:
typedef void (*ExecuteFunc)(void);
typedef struct {
const char* name;
ExecuteFunc execute;
} Action;
void jump(void) { printf("Jumping!\n"); }
void run(void) { printf("Running!\n"); }
void rest(void) { printf("Resting...\n"); }يمكنك إنشاء مصفوفة من هذه الإجراءات، كل منها مرتبط بدالة مختلفة، ثم التكرار عبرها باستخدام حلقة for بسيطة:
int main() {
Action actions[3] = {
{ "Jump", jump },
{ "Run", run },
{ "Rest", rest }
};
for (int i = 0; i < 3; i++) {
printf("%s: ", actions[i].name);
actions[i].execute();
}
return 0;
}الحلقة لا تعرف أو تهتم بما يفعله كل إجراء داخلياً. إنها ببساطة تستدعي execute() لكل عنصر.
هذا هو تعدد الأشكال (polymorphism) قيد التنفيذ — حيث يتعامل نفس الكود مع كائنات ذات سلوكيات مختلفة تمامًا. لا يتطلب إضافة نوع إجراء جديد أي تغييرات في منطق التكرار؛ فكل ما عليك فعله هو إضافة عنصر آخر إلى المصفوفة.
التحدي
سهللنقم ببناء نظام إدارة Task يوضح التكرار متعدد الأشكال (polymorphic iteration) — معالجة مجموعة من الكائنات التي تشترك في واجهة برمجية واحدة ولكنها تنفذ سلوكيات مختلفة.
ستقوم بتنظيم الكود الخاص بك عبر ثلاثة ملفات:
task.h: حدد واجهة المهام الخاصة بك هنا. قم بإنشاء نوع مؤشر دالة يسمىTaskFuncلا يأخذ أي معاملات ولا يعيد شيئاً. ثم حدد هيكلTaskيحتوي علىname(من نوعconst char*) ومؤشر دالةrunمن نوعTaskFunc.task.c: قم بتنفيذ ثلاث دوال مهام مختلفة تمثل أنواعاً مختلفة من العمل:backup_task— يطبعBacking up data...cleanup_task— يطبعCleaning up files...report_task— يطبعGenerating report...
main.c: اجمع كل شيء معاً هنا. أنشئ مصفوفة من هياكلTask، كل منها متصل بدالة مهمة مختلفة. ثم قم بالتكرار عبر المصفوفة وتنفيذ كل مهمة بشكل متعدد الأشكال — لكل مهمة، اطبع اسمها متبوعاً باستدعاء دالةrunالخاصة بها.
سيتلقى برنامجك مدخلاً واحداً: عدد المهام المطلوب تشغيلها (1، 2، أو 3).
أنشئ مصفوفة تحتوي على ثلاث مهام بالضبط بهذا الترتيب: مهمة نسخ احتياطي باسم Backup، ومهمة تنظيف باسم Cleanup، ومهمة تقرير باسم Report. بناءً على العدد المدخل، قم بالتكرار عبر هذا العدد فقط من المهام بدءاً من بداية المصفوفة.
لكل مهمة في التكرار الخاص بك، اطبع اسم المهمة متبوعاً بنقطتين ومسافة، ثم استدعِ دالة run الخاصة بها.
مثال على المخرجات عندما يكون المدخل 2:
Backup: Backing up data...
Cleanup: Cleaning up files...مثال على المخرجات عندما يكون المدخل 3:
Backup: Backing up data...
Cleanup: Cleaning up files...
Report: Generating report...الفكرة الأساسية هي أن الحلقة التكرارية الخاصة بك لا تحتاج إلى معرفة ما تفعله كل مهمة — فهي ببساطة تطبع الاسم وتستدعي run() لكل عنصر. كود التكرار نفسه يتعامل مع جميع أنواع المهام بشكل موحد. تذكر استخدام حراس التضمين (include guards) في ملف الرأس الخاص بك.
ورقة مرجعية
يمكنك التكرار عبر مجموعات من الكائنات التي تشترك في واجهة برمجية مشتركة، واستدعاء نفس مؤشر الدالة على كل عنصر بغض النظر عن تنفيذه المحدد. هذا هو تعدد الأشكال (polymorphism) — معاملة كائنات مختلفة بشكل موحد.
قم بتعريف نوع مؤشر دالة وهيكل (struct) يستخدمه:
typedef void (*ExecuteFunc)(void);
typedef struct {
const char* name;
ExecuteFunc execute;
} Action;قم بإنشاء دوال ملموسة تطابق التوقيع:
void jump(void) { printf("Jumping!\n"); }
void run(void) { printf("Running!\n"); }
void rest(void) { printf("Resting...\n"); }قم ببناء مصفوفة من الهياكل، كل منها مرتبط بدالة مختلفة، ثم قم بالتكرار عبرها:
Action actions[3] = {
{ "Jump", jump },
{ "Run", run },
{ "Rest", rest }
};
for (int i = 0; i < 3; i++) {
printf("%s: ", actions[i].name);
actions[i].execute();
}لا تحتاج الحلقة إلى معرفة ما يفعله كل إجراء داخلياً — فهي ببساطة تستدعي مؤشر الدالة في كل عنصر. إضافة أنواع جديدة لا تتطلب أي تغييرات في منطق التكرار.
جرّب بنفسك
#include <stdio.h>
#include "task.h"
// صرّح عن دوال المهام المنفذة في task.c
void backup_task(void);
void cleanup_task(void);
void report_task(void);
int main() {
int count;
scanf("%d", &count);
// TODO: أنشئ مصفوفة من 3 هياكل Task بهذا الترتيب:
// 1. مهمة النسخ الاحتياطي باسم "Backup" باستخدام backup_task
// 2. مهمة التنظيف باسم "Cleanup" باستخدام cleanup_task
// 3. مهمة التقرير باسم "Report" باستخدام report_task
// TODO: قم بالمرور عبر مهام 'count' من المصفوفة
// لكل مهمة، اطبع: "<name>: " ثم استدعِ دالة run الخاصة بها
return 0;
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات البرمجة التركيبية
ملفات الترويسةحواجز التضمينملفات المصدرالدوال الساكنةمراجعة: آلة حاسبة تركيبية4التغليف (Encapsulation)
مفهوم الـ Opaque Pointersتعريف الـ Opaque Structsالـ Getters والـ Settersالتحقق من البيانات في الـ Settersملخص: الصندوق السري2الكائنات والأساليب
الـ Structs ككائناتمؤشر 'Self'صحة استخدام Constالمؤشر مقابل القيمةالأساليب المساعدةملخص: Point Manager5مشروع: حساب بنكي بسيط
إعداد المشروعتنفيذ الحساب8تعدد الأشكال (Polymorphism)
مؤشرات الدوال في الـ Structsمحاكاة الـ Methodsمفهوم الـ Interfaceتنفيذ الـ Interfacesالتكرار متعدد الأشكالمراجعة: Greeter3دورة حياة الكائن
نمط المنشئ (Constructor Pattern)نمط الهادم (Destructor Pattern)تهيئة الـ Stackالنسخ العميق (Deep Copy)مراجعة: String Wrapper6الوراثة عبر التركيب
تضمين الـ Structقاعدة العضو الأولالوصول إلى أعضاء الأبعملية الـ Upcastingمراجعة: هرمية الأشكال9مشروع: رسّام الأشكال
نظرة عامة على المشروعتنفيذ الدائرة