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


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

تطبيق مبدأ تعددية الأشكال


لتحقيق مبدأ تعددية الأشكال مع الأصناف يجب أن نقوم بتعريف الدوال كمؤشرات، وهذا يتم من خلال إضافة الكلمة المفتاحية `كمؤشر` (as_ptr) للأمر `عملية` (handler) (بشكل مشابه للكلمة المفتاحية virtual في c++).
فلنفترض أن لدينا الصنف التالي:

صنف الصـنف {
  عملية هذا.حدد(ع: صحيح) كمؤشر { ... }
}
class A {
  handler this.set(i: Int) as_ptr { ... }
}

في التعريف أعلاه، سيضيف المترجم إلى الصنف `الـصنف` (A) مؤشراً على الدالة `حدد` (set). وهذا يُمكّن الكائنات المشتقة من هذا الصنف (التي تقوم بوراثته) من تخصيص الدالة عبر تغيير قيمة المتغير. مثلاً:

صنف ولـد {
  @حقنة عرف الصنف: الـصنف؛
  عملية هذا.حدد(ع: صحيح) حدد_مؤشر { ... }
}
class B {
  @injection def a: A;
  handler this.set(i: Int) set_ptr { ... }
}

في هذا التعريف الصنف `ولـد` (B) يشتق من `الـصنف` (A) ثم يغير مؤشر الدالة `حدد` (set) (باستخدام الكلمة المفتاحية `حدد_مؤشر` (set_ptr)) لتعريف خاص ب `ولـد`.
الآن لو كان هناك دالة تستلم سنداً أو مؤشراً على `الـصنف` (A) فيمكنك إعطاؤها سنداً ل `ولـد` (B) وستراه الدالة على أنه صنف `الصـنف` (A) مع دالة `حدد` (set) خاصة ب `ولـد` (B).

قد تكون الأمور غامضة قليلاً، لكنها ستتضح في المثال التالي...

المثال الأول:

قمنا بتعريف صنف اسمه `بلـد` (Country) يحوي 3 دوال لم يحدد متنها (body).
بعدها قمنا بتعريف صنف اسمه `كـندا` (Canada) و صنف اسمه `سـوريا` (Syria) يرثان من الصنف `بـلد` و كل منهما يحتوي تعريفاً خاصاً لنفس الدوال الموجودة في الصنف الأب `بـلد`.
بعدها قمنا بإنشاء دالة اسمها `اطبع_بيانات_البلد` (printCountryInfo) مهمتها استدعاء جميع الدوال الموجودة في الكائن الذي نمرره لها بشرط أن يكون هذا الكائن قد تم إنشاؤه من صنف يرث من الصنف Country، حيث أن هذه الدالة تستلم سنداً من الصنف الأب.
في النهاية قمنا بإنشاء كائن من الصنف `كـندا` (Canada) و كائن من الصنف `سـوريا` (Syria) و تمرير كل كائن منهما للدالة `اطبع_بيانات_البلد` (printCountryInfo).

اشمل "مـتم/طـرفية"؛
استخدم مـتم.طـرفية؛

// تعريف صنف يمثل بلد
صنف بـلد {
  // دالة سنستخدمها لطباعة اسم البلد
  عملية هذا.اسم() كمؤشر؛
  // دالة سنستخدمها لطباعة العاصمة
  عملية هذا.عاصمة() كمؤشر؛
  // دالة سنستخدمها لطباعة اللغة
  عملية هذا.لغة() كمؤشر؛
}
// تعريف صنف يمثل بلد محدد هو كندا
صنف كـندا {
  // سنقوم الآن بوراثة الصنف `بـلد`
  @حقنة عرف بلد: بـلد؛
  // تعريفاً مخصصاً بـلد الآن سنقوم بتعريف الدوال الموجودة في الصنف
  عملية (هذا:بـلد).اسم() حدد_مؤشر {
    اطبع("البلد: كـندا\ج")؛
  }
  عملية (هذا:بـلد).عاصمة() حدد_مؤشر {
    اطبع("العاصمة: أوتاوا\ج")؛
  }
  عملية (هذا:بـلد).لغة() حدد_مؤشر {
    اطبع("اللغة: الانجليزية\ج")؛
  }
}
// تعريف صنف آخر يمثل بلد محدد هو سوريا
صنف سـوريا {
  @حقنة عرف بلد: بـلد؛
  عملية (هذا:بـلد).اسم() حدد_مؤشر {
    اطبع("البلد: سـوريا\ج")؛
  }
  عملية (هذا:بـلد).عاصمة() حدد_مؤشر {
    اطبع("العاصمة: دمشق\ج")؛
  }
  عملية (هذا:بـلد).لغة() حدد_مؤشر {
    اطبع("اللغة: العربية\ج")؛
  }
}

// دالة تقوم باستلام سند من الصنف بـلد
// ثم في متن هذه الدالة سنقوم باستخدام السند للوصول إلى الدوال
دالة اطبع_بيانات_البلد(س: سند[بـلد]) {
  س.اسم()؛ // تؤدي إلى طباعة اسم بلد
  س.عاصمة()؛ // العاصمة
  س.لغة()؛ // اللغة
  اطبع("********************\ج")؛
}

عرف كائن1: كـندا؛
// لطباعة معلومات هذا البلد اطبع_بيانات_البلد سنقوم الآن بتمريره إلى الدالة
اطبع_بيانات_البلد(كائن1)؛

// سـوريا سنكرر الآن نفس الأمر لكن مع كائن من الصنف
عرف كائن2: سـوريا؛
اطبع_بيانات_البلد(كائن2)؛
/*
البلد: كـندا
العاصمة: أوتاوا
اللغة: الانجليزية
********************
البلد: سوريا
العاصمة: دمشق
اللغة: العربية
********************
*/
import "Srl/Console.alusus"
use Srl.Console;
// تعريف صنف يمثل بلد
class Country {
  // دالة سنستخدمها لطباعة اسم البلد
  handler this.name() as_ptr;
  // دالة سنستخدمها لطباعة العاصمة
  handler this.capital() as_ptr;
  // دالة سنستخدمها لطباعة اللغة
  handler this.language() as_ptr;
}
// تعريف صنف يمثل بلد محدد هو كندا
class Canada {
  // country سنقوم الآن بوراثة الصنف
  @injection def country: Country;
  // تعريفاً مخصصاً Country الآن سنقوم بتعريف الدوال الموجودة في الصنف
  handler (this:Country).name() set_ptr {
    print("Country: Canada\n");
  }
  handler (this:Country).capital() set_ptr {
    print("Capital: Ottawa\n");
  }
  handler (this:Country).language() set_ptr {
    print("Language: English\n");
  }
}
// تعريف صنف آخر يمثل بلد محدد هو سوريا
class Syria {
  @injection def country: Country;
  handler (this:Country).name() set_ptr {
    print("Country: Syria\n");
  }
  handler (this:Country).capital() set_ptr {
    print("Capital: Damascus\n");
  }
  handler (this:Country).language() set_ptr {
    print("Language: Arabic\n");
  }
}

// Country دالة تقوم باستلام سند من الصنف
// ثم في متن هذه الدالة سنقوم باستخدام السند للوصول إلى الدوال
function printCountryInfo(re:ref[Country]) {
  re.name(); // تؤدي إلى طباعة اسم بلد
  re.capital(); // العاصمة
  re.language(); // اللغة
  print("********************\n");
}

def obj1: Canada;
// لطباعة معلومات هذا البلد printCountryInfo سنقوم الآن بتمريره إلى الدالة
printCountryInfo(obj1);

// Syria سنكرر الآن نفس الأمر لكن مع كائن من الصنف
def obj2: Syria;
printCountryInfo(obj2);
/*
Country: Canada
Capital: Ottawa
Language: English
********************
Country: Syria
Capital: Damascus
Language: Arabic
********************
*/

هكذا نكون قد كتبنا دالة بسيطة تحقق مبدأ تعددية الأشكال.

المثال الثاني:

سنقوم باستخدام نفس المثال السابق لكن مع المصفوفات الديناميكية.

اشمل "مـتم/طـرفية"؛
اشمل "مـتم/مـصفوفة"؛
استخدم مـتم؛

صنف بـلد {
  عملية هذا.اسم() كمؤشر؛
  عملية هذا.عاصمة() كمؤشر؛
  عملية هذا.لغة() كمؤشر؛
}

صنف كـندا {
  @حقنة عرف بلد: بـلد؛
  عملية (هذا:بـلد).اسم() حدد_مؤشر {
    طـرفية.اطبع("البلد: كندا\ج")؛
  }
  عملية (هذا:بـلد).عاصمة() حدد_مؤشر {
    طـرفية.اطبع("العاصمة: أوتاوا\ج")؛
  }
  عملية (هذا:بـلد).لغة() حدد_مؤشر {
    طـرفية.اطبع("اللغة: الانجليزية\ج")؛
  }
}

صنف سـوريا {
  @حقنة عرف بلد: بـلد؛
  عملية (هذا:بـلد).اسم() حدد_مؤشر {
    طـرفية.اطبع("البلد: سوريا\ج")؛
  }
  عملية (هذا:بـلد).عاصمة() حدد_مؤشر {
    طـرفية.اطبع("العاصمة: دمشق\ج")؛
  }
  عملية (هذا:بـلد).لغة() حدد_مؤشر {
    طـرفية.اطبع("اللغة: العربية\ج")؛
  }
}

دالة اطبع_بيانات_البلد(ب: سند[بـلد]) {
  ب.اسم()؛
  ب.عاصمة()؛
  ب.لغة()؛
  طـرفية.اطبع("********************\n")؛
}
// بـلد تعريف مصفوفة ديناميكية كل عنصر فيها يمثل سند من الصنف
عرف بلدان: مـصفوفة[سند[بـلد]]؛
عرف كائن1: كـندا؛
عرف كائن2: سـوريا؛
عرف ع: صحيح؛
بلدان.أضف(كائن1)؛ // إضافة الكائن الأول إلى المصفوفة
بلدان.أضف(كائن2)؛ // إضافة الكائن الثاني
// اطبع_بيانات_البلد الآن سنقوم بالمرور على عناصر المصفوفة وتمريرهم إلى الدالة
لكل ع = 0، ع < بلدان.هات_الطول(), ع++ {
  اطبع_بيانات_البلد(بلدان(ع))؛
}
/*
البلد: كندا
العاصمة: أوتاوا
اللغة: الانجليزية
********************
البلد: سوريا
العاصمة: دمشق
اللغة: العربية
********************
*/
import "Srl/Console.alusus"
import "Srl/Array.alusus";
use Srl;

class Country {
  handler this.name() as_ptr;
  handler this.capital() as_ptr;
  handler this.language() as_ptr;
}

class Canada {
  @injection def country: Country;
  handler (this:Country).name() set_ptr {
    Console.print("Country: Canada\n");
  }
  handler (this:Country).capital() set_ptr {
    Console.print("Capital: Ottawa\n");
  }
  handler (this:Country).language() set_ptr {
    Console.print("Language: English\n");
  }
}

class Syria {
  @injection def country: Country;
  handler (this:Country).name() set_ptr {
    Console.print("Country: Syria\n");
  }
  handler (this:Country).capital() set_ptr {
    Console.print("Capital: Damascus\n");
  }
  handler (this:Country).language() set_ptr {
    Console.print("Language: Arabic\n");
  }
}

function printCountryInfo(re:ref[Country]) {
  re.name()
  re.capital()
  re.language()
  Console.print("********************\n")
}
// Country تعريف مصفوفة ديناميكية كل عنصر فيها يمثل سند من الصنف
def countries: Array[ref[Country]];
def obj1: Canada;
def obj2: Syria;
def i: int;
countries.add(obj1); // إضافة الكائن الأول إلى المصفوفة
countries.add(obj2); // إضافة الكائن الثاني
// printCountryInfo الآن سنقوم بالمرور على عناصر المصفوفة وتمريرهم إلى الدالة
for i=0,i < countries.getLength(), i++ {
  printCountryInfo(countries(i));
}
/*
Country: Canada
Capital: Ottawa
Language: English
********************
Country: Syria
Capital: Damascus
Language: Arabic
********************
*/