الوراثة المتعددة

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

لطالما شكّلت الوراثة المتعددة قضيةً مثيرةً للجدل لسنوات عديدة، [ 1 ] [ 2 ] حيث يشير المعارضون إلى تعقيدها المتزايد والغموض الذي يكتنفها في حالات مثل "مشكلة المعين"، حيث قد يكون من غير الواضح من أي فئة أصلية تُورَث سمة معينة إذا كانت أكثر من فئة أصلية تُنفّذ تلك السمة. ويمكن معالجة هذا الأمر بطرق مختلفة، بما في ذلك استخدام الوراثة الافتراضية . [ 3 ] كما اقتُرحت طرق بديلة لتكوين الكائنات لا تعتمد على الوراثة، مثل المزيجات والسمات ، لمعالجة هذا الغموض.

تفاصيل

في البرمجة كائنية التوجه (OOP)، يصف التوريث علاقة بين فئتين، حيث ترث إحداهما ( الفئة الفرعية ) من الفئة الأصلية (أو ترث منها أو تتوسع منها ) . ترث الفئة الفرعية خصائص وأساليب الفئة الأصلية، مما يسمح بمشاركة الوظائف. على سبيل المثال، يمكن إنشاء فئة متغيرة بخصائص مثل الأكل والتكاثر، ثم تعريف فئة فرعية ترث هذه الخصائص دون الحاجة إلى برمجتها بشكل صريح، مع إضافة خصائص جديدة مثل مطاردة الفئران .MammalCat

يسمح التوريث المتعدد للمبرمجين باستخدام أكثر من تسلسل هرمي متعامد تمامًا في وقت واحد، مثل السماح Catبتوسيع CartoonCharacter، Petوالوصول Mammalإلى الميزات من داخل جميع تلك الفئات.

التطبيقات

تشمل اللغات التي تدعم الوراثة المتعددة ما يلي: C++ ، Common Lisp (عبر نظام كائنات Common Lisp (CLOS))، EuLisp (عبر نظام كائنات EuLisp TELOS)، Curl ، Dylan ، Eiffel ، Logtalk ، Object REXX ، Scala (عبر استخدام فئات mixinOCaml ، Perl ، POP-11 ، Python ، R ، Raku ، و Tcl (مدمجة من الإصدار 8.6 أو عبر Incremental Tcl ( Incr Tcl ) في الإصدارات السابقة [ 4 ] [ 5 ] ).

يدعم وقت تشغيل نموذج كائنات النظام (SOM) من IBM التوريث المتعدد، ويمكن لأي لغة برمجة تستهدف SOM تنفيذ فئات SOM جديدة موروثة من قواعد متعددة.

بعض اللغات الموجهة للكائنات، مثل Swift و Java و Fortran منذ مراجعتها في عام 2003 و C# و Ruby ، تطبق الوراثة الفردية ، على الرغم من أن البروتوكولات أو الواجهات توفر بعض وظائف الوراثة المتعددة الحقيقية.

تستخدم لغة PHP فئات السمات لتوريث تطبيقات طرق محددة. بينما تستخدم لغة Ruby الوحدات النمطية لتوريث طرق متعددة.

مشكلة الألماس

مخطط وراثة فئة الماس

تُعرف " مشكلة الألماس " (التي يُشار إليها أحيانًا باسم "ألماس الموت القاتل" [ 6 ] ) بأنها غموض ينشأ عندما ترث فئتان B و C من الفئة A، وترث الفئة D من كل من B و C. إذا كانت هناك طريقة في A قامت B و C بتجاوزها ، ولم تقم D بتجاوزها، فأي نسخة من الطريقة ترثها D: نسخة B، أم نسخة C؟

على سبيل المثال، في سياق تطوير برمجيات واجهة المستخدم الرسومية ، قد يرث صنفٌ ما من صنفين آخرين (للمظهر) و (للوظائف/معالجة المدخلات)، ويرث الصنفان و من الصنف . الآن، إذا تم استدعاء دالةٍ لكائنٍ ما ، ولم تكن هذه الدالة موجودةً في الصنف ، ولكن توجد دالةٌ مُعاد تعريفها في الصنف أو (أو كليهما)، فأي دالةٍ يجب استدعاؤها في النهاية؟ButtonRectangleClickableRectangleClickableObjectequalsButtonButtonequalsRectangleClickable

تُسمى هذه المسألة "مسألة المعين" نسبةً إلى شكل مخطط وراثة الفئات في هذه الحالة. ففي هذه الحالة، تقع الفئة A في الأعلى، وتليها الفئتان B وC بشكل منفصل، بينما تربط الفئة D بينهما في الأسفل لتشكيل شكل معين.

التخفيف

تختلف اللغات في طرق تعاملها مع مشاكل التوريث المتكرر.

  • تسمح لغة C# (منذ الإصدار 8.0) بتنفيذ أساليب الواجهة افتراضيًا، مما يؤدي إلى وجود أسلوبين "موروثين" بنفس التوقيع في فئة Aتُنفذ واجهات Iaولها أساليب متشابهة ذات تنفيذ افتراضي، وهو ما يُعرف بمشكلة المعين. يُمكن التخفيف من هذه المشكلة إما باشتراط تنفيذ الأسلوب نفسه، وبالتالي إزالة الغموض، أو بإجبار المُستدعي على تحويل الكائن أولًا إلى الواجهة المناسبة لاستخدام تنفيذه الافتراضي لهذا الأسلوب (على سبيل المثال ).IbAA((Ia) aInstance).Method();
  • تتبع لغة C++ افتراضيًا كل مسار وراثة على حدة. لذا، في حالة مشكلة المعين، Dسيحتوي الكائن في الواقع على Aكائنين منفصلين، ويجب تحديد استخدامات Aأعضاء الكائن بشكل صحيح. إذا تم تمييز الوراثة من Aالكائن إلى الكائن الآخر ، وكذلك Bالوراثة من Aالكائن إلى الكائن الثالث، فإن C++ تحرص على إنشاء كائن واحد فقط، وتعمل استخدامات أعضاء الكائن الثالث بشكل صحيح. في حالة اختلاط الوراثة الافتراضية وغير الافتراضية، يوجد كائن افتراضي واحد ، وكائن غير افتراضي لكل مسار وراثة غير افتراضي إلى الكائن الثالث . تتطلب C++ تحديد الفئة الأصلية التي يتم استدعاء الميزة منها بشكل صريح، أي . لا تدعم C++ الوراثة المتكررة الصريحة لأنه لن تكون هناك طريقة لتحديد الفئة الأصلية المراد استخدامها (أي ظهور فئة أكثر من مرة في قائمة اشتقاق واحدة ). تسمح C++ أيضًا بإنشاء نسخة واحدة من الفئة المتعددة عبر آلية الوراثة الافتراضية (أي أن الكائنين سيشيران إلى نفس الكائن).Cvirtualclass B : virtual public AAAAAAWorker::Human.ageclass Dog : public Animal, AnimalWorker::HumanMusician::Human
  • تسعى لغة Common Lisp CLOS إلى توفير سلوك افتراضي معقول مع إمكانية تعديله. ببساطة، يتم ترتيب الدوال افتراضيًا D,B,C,A، حيث تُكتب B قبل C في تعريف الصنف. تُختار الدالة ذات الوسائط الأكثر تحديدًا (D>(B,C)>A)  ؛ ثم تُرتّب الدوال حسب ترتيب تسمية الأصناف الأصلية في تعريف الصنف الفرعي (B>C). مع ذلك، يمكن للمبرمج تعديل هذا الترتيب بتحديد ترتيب معين لحل الدوال أو وضع قاعدة لدمجها. يُسمى هذا دمج الدوال، ويمكن التحكم به بشكل كامل. كما يوفر بروتوكول الكائنات الوصفية (MOP ) وسائل لتعديل الوراثة، والتوزيع الديناميكي ، وإنشاء الأصناف، وآليات داخلية أخرى دون التأثير على استقرار النظام.
  • لا يسمح Curl إلا بتوريث الفئات التي تم تحديدها صراحةً على أنها مشتركة بشكل متكرر. يجب أن تُعرّف الفئات المشتركة مُنشئًا ثانويًا لكل مُنشئ عادي في الفئة. يتم استدعاء المُنشئ العادي عند تهيئة حالة الفئة المشتركة لأول مرة من خلال مُنشئ فئة فرعية، وسيتم استدعاء المُنشئ الثانوي لجميع الفئات الفرعية الأخرى.
  • في لغة إيفل ، تُختار خصائص الفئات الأصلية صراحةً باستخدام توجيهات select و rename. يتيح ذلك مشاركة خصائص الفئة الأساسية بين الفئات الفرعية، أو منح كل فئة فرعية نسخة منفصلة من الفئة الأساسية. تسمح إيفل بدمج أو فصل الخصائص الموروثة من الفئات الأصلية بشكل صريح. تقوم إيفل تلقائيًا بدمج الخصائص معًا إذا كانت تحمل نفس الاسم والتنفيذ. يمتلك مُنشئ الفئة خيار إعادة تسمية الخصائص الموروثة لفصلها. يُعدّ التوريث المتعدد أمرًا شائعًا في تطوير إيفل؛ فمعظم الفئات الفعّالة في مكتبة EiffelBase واسعة الانتشار لهياكل البيانات والخوارزميات، على سبيل المثال، لها فئتان أصليتان أو أكثر. [ 7 ]
  • تتجنب لغة Go مشكلة المعين أثناء الترجمة . إذا احتوى هيكل Dما على هيكلين B، Cوكلاهما يمتلكان دالة F()، وبالتالي يحققان واجهة A، فسيُصدر المترجم تحذيرًا بشأن "محدد غامض" إذا تم استدعاء الدالة، أو إذا تم إسناد D.F()نسخة من الهيكل إلى متغير من النوع . ويمكن استدعاء دوال الهيكل صراحةً باستخدام أو .DABCD.B.F()D.C.F()
  • يُقدّم Java 8 الدوال الافتراضية في الواجهات. إذا Aكانت ` <sub>1 B</sub>` و` <sub>2</sub>` و` <sub>3</sub>` Cواجهات، فيمكن لكلٍّ منها توفير تطبيق مختلف لدالة مجردة من `<sub>1</sub>`، مما يُسبب مشكلة المعين. يجب على أيٍّ من الفئتين إعادة تطبيق الدالة (حيث يمكن ببساطة توجيه استدعاء الدالة إلى أحد تطبيقات الفئة الأصلية)، وإلا سيتم رفض الغموض كخطأ في الترجمة. [ 8 ] قبل Java 8، لم تكن Java مُعرَّضة لخطر مشكلة المعين، لأن الدوال الافتراضية للواجهات لم تكن مُتاحة. جميع إصدارات Java حتى الإصدار 26 الأخير لا تدعم الوراثة المتعددة بشكل مباشر: يمكن للفئة أن ترث من فئة واحدة فقط، ولكن يمكنها تطبيق واجهات متعددة باستخدام الدوال الافتراضية.BCAD
  • يسمح JavaFX Script في الإصدار 1.2 بالوراثة المتعددة باستخدام mixins . في حالة حدوث تعارض، يمنع المُصرّف الاستخدام المباشر للمتغير أو الدالة الغامضة. مع ذلك، يمكن الوصول إلى كل عضو موروث عن طريق تحويل الكائن إلى mixin المطلوب، على سبيل المثال (individual as Person).printInfo();.
  • تسمح لغة Kotlin بتعدد وراثة الواجهات، ولكن في حالة مشكلة الماسة، يجب على الفئة الفرعية تجاوز الطريقة التي تسبب تعارض الوراثة وتحديد أي تطبيق للفئة الأصلية يجب استخدامه. على سبيل المثال super<ChosenParentInterface>.someMethod()
  • يدعم Logtalk كلاً من التوريث المتعدد للواجهة والتنفيذ، مما يسمح بإعلان أسماء مستعارة للأساليب التي توفر إعادة التسمية والوصول إلى الأساليب التي سيتم إخفاؤها بواسطة آلية حل التعارض الافتراضية.
  • في لغة OCaml ، تُحدد الفئات الأصلية بشكل فردي في متن تعريف الفئة. تُورَث الطرق (والخصائص) بنفس الترتيب، حيث تُستبدل كل طريقة موروثة حديثًا بأي طرق موجودة. تختار OCaml آخر تعريف مطابق من قائمة وراثة الفئة لتحديد تنفيذ الطريقة المراد استخدامه في حالات الغموض. لتجاوز السلوك الافتراضي، يكفي ببساطة تحديد استدعاء الطريقة بتعريف الفئة المطلوب.
  • تستخدم لغة بيرل قائمة الفئات المراد الوراثة منها كقائمة مرتبة. يستخدم المترجم أول دالة يجدها من خلال البحث العميق أولاً في قائمة الفئات الأصلية أو باستخدام خوارزمية C3 لترتيب الفئات. توفر العديد من الإضافات مخططات بديلة لتكوين الفئات. يؤثر ترتيب الوراثة على دلالات الفئات. في حالة الغموض المذكورة أعلاه، Bيتم فحص الفئة وأسلافها قبل الفئة Cوأسلافها، وبالتالي Aيتم وراثة الدالة من خلالها B. هذا السلوك مشترك مع Io و Picolisp . في بيرل، يمكن تجاوز هذا السلوك باستخدام mroوحدات أخرى لاستخدام خوارزمية C3 أو خوارزميات أخرى. [ 9 ]
  • تتمتع لغة بايثون بنفس بنية لغة بيرل، ولكنها، على عكس بيرل، تُضمّنها في بناء جملة اللغة. يؤثر ترتيب الوراثة على دلالات الأصناف. وقد واجهت بايثون هذه المشكلة عند ظهور الأصناف الجديدة، التي تشترك جميعها في سلف مشترك object. تُنشئ بايثون قائمة بالأصناف باستخدام خوارزمية الترتيب الخطي C3 (أو ترتيب حل الأساليب (MRO)). تفرض هذه الخوارزمية قيدين: الأول هو أن الأبناء يسبقون آباءهم، والثاني هو أنه إذا ورث صنف من عدة أصناف، فإنه يُحتفظ بها بالترتيب المحدد في مجموعة الأصناف الأساسية (مع ذلك، في هذه الحالة، قد تسبق بعض الأصناف الموجودة في أعلى مخطط الوراثة الأصناف الموجودة في أسفل المخطط [ 10 ] ). وبالتالي، يكون ترتيب حل الأساليب كالتالي: D، B، C، A. [ 11 ]
  • تحتوي فئات روبي على فئة أصلية واحدة فقط، ولكنها قد ترث أيضًا من وحدات متعددة؛ تُنفَّذ تعريفات فئات روبي، ويؤدي (إعادة) تعريف أي دالة إلى إخفاء أي تعريف سابق لها وقت التنفيذ. في غياب البرمجة الوصفية أثناء التشغيل، يكون لهذا الأمر دلالات مشابهة تقريبًا لخوارزمية البحث العميق من اليمين أولًا.
  • تسمح لغة سكالا بإنشاء نسخ متعددة من السمات ، مما يتيح التوريث المتعدد من خلال التمييز بين التسلسل الهرمي للفئات والتسلسل الهرمي للسمات. لا يمكن للفئة أن ترث إلا من فئة واحدة، ولكن يمكنها دمج أي عدد من السمات حسب الرغبة. تستخدم سكالا خوارزمية بحث متعمق (من اليمين إلى اليسار) لحلّ أسماء الدوال في قائمة السمات الموسعة، قبل حذف جميع التكرارات باستثناء آخر تكرار لكل وحدة في القائمة الناتجة. لذا، يكون ترتيب الحل كالتالي: [ D, C, A, B, A]، والذي يختزل إلى [ D, C, B, A].
  • تسمح لغة Tcl بوجود فئات أصلية متعددة؛ ويؤثر ترتيب تحديدها في تعريف الفئة على حل أسماء الأعضاء باستخدام خوارزمية C3 للترتيب الخطي . [ 12 ]

اللغات التي تسمح بالوراثة الأحادية فقط ، حيث لا يمكن للفئة أن ترث إلا من فئة أساسية واحدة، لا تعاني من مشكلة المعين. والسبب في ذلك هو أن هذه اللغات تحتوي على تطبيق واحد على الأكثر لأي دالة في أي مستوى من سلسلة الوراثة، بغض النظر عن تكرار الدوال أو موضعها. عادةً ما تسمح هذه اللغات للفئات بتطبيق بروتوكولات متعددة ، تُسمى واجهات في جافا. تُعرّف هذه البروتوكولات الدوال ، لكنها لا تُقدّم تطبيقات ملموسة. وقد استُخدمت هذه الاستراتيجية في لغات مثل ActionScript و C# و D وجافا و Nemerle و Object Pascal و Objective-C و Smalltalk و Swift و PHP . [ 13 ] جميع هذه اللغات تسمح للفئات بتطبيق بروتوكولات متعددة.

علاوة على ذلك، تسمح لغات Ada وC# وJava وObject Pascal وObjective-C وSwift وPHP بالتوريث المتعدد للواجهات (المسماة بروتوكولات في Objective-C وSwift). تُشبه الواجهات فئات أساسية مجردة تُحدد توقيعات الدوال دون تنفيذ أي سلوك. (لا تسمح الواجهات "الخالصة"، مثل تلك الموجودة في Java حتى الإصدار 7، بأي بيانات تنفيذ أو مثيل داخل الواجهة). مع ذلك، حتى عندما تُعلن عدة واجهات عن نفس توقيع الدالة، فبمجرد تنفيذ (تعريف) تلك الدالة في أي مكان في سلسلة التوريث، فإنها تُلغي أي تنفيذ لتلك الدالة في السلسلة الأعلى منها (في فئاتها الأصلية). وبالتالي، في أي مستوى مُحدد في سلسلة التوريث، لا يُمكن أن يكون هناك أكثر من تنفيذ واحد لأي دالة. لذا، لا يُظهر تنفيذ الدالة في حالة التوريث الأحادي مشكلة المعين حتى مع التوريث المتعدد للواجهات. مع إدخال التنفيذ الافتراضي للواجهات في Java 8 وC# 8، لا يزال من الممكن توليد مشكلة المعين، على الرغم من أن هذا سيظهر فقط كخطأ في وقت الترجمة.

انظر أيضاً

مراجع

  1. كارغيل، تي إيه (شتاء 1991). "جدل: الحجة ضد التوريث المتعدد في لغة سي++". أنظمة الحوسبة . 4 (1): 69-82 .
  2. والدو، جيم (ربيع 1991). "جدل: حالة الوراثة المتعددة في لغة C++". أنظمة الحوسبة . 4 (2): 157-171 .
  3. شارلي، ناثانيل؛ دوكاس، ستيفان؛ نيرستراز، أوسكار؛ بلاك، أندرو. "السمات: وحدات سلوكية قابلة للتركيب" (ملف PDF) . Web.cecs.pdx.edu . تاريخ الاسترجاع: 21 أكتوبر 2016 .
  4. "incr Tcl" . blog.tcl.tk. تم الاطلاع عليه بتاريخ 14 أبريل 2020 .
  5. "مقدمة إلى لغة برمجة Tcl" . www2.lib.uchicago.edu . تاريخ الاسترجاع: 14 أبريل 2020 .
  6. مارتن، روبرت سي. (9 مارس 1997). "جافا وسي++: مقارنة نقدية" (ملف PDF) . Objectmentor.com . مؤرشف من الأصل (ملف PDF) بتاريخ 24 أكتوبر 2005. تم الاطلاع عليه بتاريخ 21 أكتوبر 2016 .
  7. "المعيار ECMA-367" . Ecma-international.org . تم الاطلاع عليه بتاريخ 21-10-2016 .
  8. "حالة لامدا" . Cr.openjdk.java.net . تم الاطلاع عليه بتاريخ 21-10-2016 .
  9. "perlobj" . perldoc.perl.org . تم الاطلاع عليه بتاريخ 21-10-2016 .
  10. ملخص. "ترتيب حل الدوال في بايثون 2.3" . Python.org . تم الاطلاع عليه بتاريخ 21-10-2016 .
  11. "توحيد الأنواع والفئات في بايثون 2.2" . Python.org . تم الاطلاع عليه بتاريخ 21-10-2016 .
  12. "Manpage of class" . Tcl.tk. 1999-11-16 . تم الاسترجاع في 2016-10-21 .
  13. "واجهات الكائنات - دليل المستخدم" . PHP.net . 2007-07-04 . تم الاطلاع عليه بتاريخ 2016-10-21 .

للمزيد من القراءة