إرسال مزدوج

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

وصف دان إنجلز لأول مرة كيفية استخدام الإرسال المزدوج في سمول توك ، وأطلق عليه اسم تعدد الأشكال المتعدد . [ 1 ]

ملخص

تتمثل المشكلة العامة التي يتم تناولها في كيفية إرسال رسالة إلى طرق مختلفة اعتمادًا ليس فقط على المتلقي ولكن أيضًا على الوسائط.

ولتحقيق هذه الغاية، تُطبّق أنظمة مثل CLOS خاصية الإرسال المتعدد . ويُعدّ الإرسال المزدوج حلاً آخر يُقلّل تدريجياً من تعدد الأشكال في الأنظمة التي لا تدعم الإرسال المتعدد.

حالات الاستخدام

يُعدّ التوزيع المزدوج مفيدًا في الحالات التي يعتمد فيها اختيار العملية الحسابية على أنواع الوسائط المُدخلة أثناء التشغيل. على سبيل المثال، يمكن للمبرمج استخدام التوزيع المزدوج في الحالات التالية:

  • فرز مجموعة مختلطة من العناصر: تتطلب الخوارزميات فرز قائمة العناصر وفق ترتيب معياري. ويتطلب تحديد ما إذا كان عنصر ما يسبق عنصرًا آخر معرفة كلا النوعين، وربما بعض الحقول الفرعية.
  • تتطلب خوارزميات التصادم التكيفية عادةً معالجة التصادمات بين الأجسام المختلفة بطرق مختلفة. ومن الأمثلة الشائعة على ذلك بيئة الألعاب، حيث يتم حساب التصادم بين مركبة فضائية وكويكب بشكل مختلف عن حساب التصادم بين مركبة فضائية ومحطة فضائية. [ 2 ]
  • خوارزميات الرسم التي تتطلب عرض نقاط تقاطع الصور المتداخلة بطريقة مختلفة.
  • قد تُسند أنظمة إدارة شؤون الموظفين أنواعًا مختلفة من الوظائف إلى موظفين مختلفين. scheduleفعلى سبيل المثال، إذا أُعطيت خوارزمية كائنًا لشخص مصنف كمحاسب وكائنًا لوظيفة مصنفة كمهندس، فإنها سترفض جدولة ذلك الشخص لتلك الوظيفة.
  • أنظمة معالجة الأحداث التي تستخدم كلاً من نوع الحدث ونوع كائن المستقبل من أجل استدعاء روتين معالجة الأحداث الصحيح.
  • في أنظمة الأقفال والمفاتيح، تتعدد أنواع الأقفال والمفاتيح، حيث يفتح كل نوع من المفاتيح أنواعًا متعددة من الأقفال. ولا يقتصر الأمر على معرفة أنواع الأشياء المعنية، بل إن مجموعة المعلومات المتعلقة بمفتاح معين، والتي تُحدد ما إذا كان هذا المفتاح يفتح قفلًا معينًا، تختلف باختلاف أنواع الأقفال.

مصطلح شائع

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

أمثلة

روبي

من الاستخدامات الشائعة عرض كائن على منفذ عرض، سواءً كان شاشة أو طابعة، أو أي شيء آخر غير موجود بعد. هذه طريقة بدائية للتعامل مع هذه الوسائط المختلفة.

class Rectangle def display_on ( port ) # يختار الكود المناسب بناءً على فئة الكائن case port when DisplayPort # كود للعرض على DisplayPort when PrinterPort # كود للعرض على PrinterPort when RemotePort # كود للعرض على RemotePort end end end

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

الحل الأكثر وضوحًا وسهولة في الصيانة هو إجراء عملية إرسال ثانية، هذه المرة لاختيار الطريقة الصحيحة لعرض الكائن على الوسيط:

class Rectangle def display_on ( port ) # الإرسال الثاني port.display_rectangle ( self ) end endclass Oval def display_on ( port ) # إرسال ثانٍ port.display_oval ( self ) end endclass DisplayPort def display_rectangle ( object ) # كود لعرض مستطيل على منفذ DisplayPort end def display_oval ( object ) # كود لعرض شكل بيضاوي على منفذ DisplayPort end # ... endclass PrinterPort def display_rectangle ( object ) # كود لعرض مستطيل على PrinterPort end def display_oval ( object ) # كود لعرض شكل بيضاوي على PrinterPort end # ... end

لغة سي++

للوهلة الأولى، يبدو أن الإرسال المزدوج نتيجة طبيعية لتحميل الدوال الزائد . يسمح تحميل الدوال الزائد للدالة المستدعاة بالاعتماد على نوع الوسيط. مع ذلك، يتم تحميل الدوال الزائد في وقت الترجمة باستخدام " تغيير أسماء الدوال "، حيث يُشفّر الاسم الداخلي للدالة نوع الوسيط. على سبيل المثال، foo(int)قد تُسمى دالة داخليًا __foo_ifoo(double) ، بينما قد تُسمى دالة أخرى __foo_d . بالتالي، لا يوجد تصادم في الأسماء، ولا حاجة للبحث في جدول الدوال الافتراضية. في المقابل، يعتمد الإرسال الديناميكي على نوع الكائن المُستدعي، أي أنه يستخدم الدوال الافتراضية (التجاوز) بدلًا من تحميل الدوال الزائد ، وينتج عنه بحث في جدول الدوال الافتراضية. لننظر إلى المثال التالي، المكتوب بلغة C++ ، لتصادمات في لعبة:

class Spaceship {}; class ApolloSpacecraft : public Spaceship {};class Asteroid { public : virtual void collideWith ( Spaceship & ) { std :: println ( "اصطدم الكويكب بمركبة فضائية" ); }virtual void collideWith ( ApolloSpacecraft & ) { std :: println ( "اصطدم كويكب بمركبة أبولو الفضائية" ); } };class ExplodingAsteroid : public Asteroid { public : void collideWith ( Spaceship & ) override { std :: println ( "اصطدم الكويكب المتفجر بمركبة فضائية" ); }void collideWith ( ApolloSpacecraft & ) override { std :: println ( "اصطدم كويكب متفجر بمركبة أبولو الفضائية" ); } };

إذا كان لديك:

كويكب ؛ مركبة فضائية ؛ مركبة أبولو الفضائية ؛

ثم، بسبب التحميل الزائد للوظائف،

asteroid.collideWith ( spaceship ) ; asteroid.collideWith ( apolloSpacecraft ) ;

Asteroid hit a Spaceshipسيتم طباعة كل من و ، على التوالي Asteroid hit an ApolloSpacecraft، دون استخدام أي إرسال ديناميكي. علاوة على ذلك:

انفجر الكويكب ؛ انفجر الكويكب . اصطدم بـ ( مركبة فضائية انفجر الكويكب . اصطدم بـ ( مركبة أبولو الفضائية 

سيتم الطباعة ExplodingAsteroid hit a Spaceshipعلى ExplodingAsteroid hit an ApolloSpacecraftالتوالي، مرة أخرى بدون إرسال ديناميكي.

باستخدام مرجع إلى Asteroid، يتم استخدام الإرسال الديناميكي، وهذا الكود:

Asteroid & asteroidRef = explodingAsteroid ; asteroidRef . collideWith ( spaceship ); asteroidRef . collideWith ( apolloSpacecraft );

يطبع النص ExplodingAsteroid hit a Spaceship، ExplodingAsteroid hit an ApolloSpacecraftكما هو متوقع. ومع ذلك، فإن الكود التالي لا يعمل كما هو مطلوب:

المركبة الفضائية & spaceshipRef = apolloSpacecraft ; asteroid . collideWith ( spaceshipRef ); asteroidRef . collideWith ( spaceshipRef );

السلوك المطلوب هو ربط هذه الاستدعاءات بالدالة التي تأخذ apolloSpacecraftكوسيط، لأن هذا هو نوع المتغير المُنشأ، مما يعني أن الناتج المتوقع سيكون `x` Asteroid hit an ApolloSpacecraftو`y` ExplodingAsteroid hit an ApolloSpacecraft. مع ذلك، الناتج الفعلي هو `x` Asteroid hit a Spaceshipو`y` ExplodingAsteroid hit a Spaceship. تكمن المشكلة في أنه بينما يتم استدعاء الدوال الافتراضية ديناميكيًا في لغة C++، فإن تحميل الدوال الزائد يتم بشكل ثابت.

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

virtual void collideWith ( Asteroid & asteroid ) { asteroid . collideWith ( * this ); }

ثم، بينما لا يزال المثال السابق لا يعمل بشكل صحيح، فإن إعادة صياغة المكالمات بحيث تكون المركبة الفضائية هي الفاعل تعطينا السلوك المطلوب:

مركبة فضائية & spaceshipRef = apolloSpacecraft ؛ كويكب & asteroidRef = explodingAsteroid ؛ spaceshipRef.collideWith ( asteroid ) ؛ spaceshipRef.collideWith ( asteroidRef ) ؛

يطبع البرنامج القيمتين Asteroid hit an ApolloSpacecraftو ExplodingAsteroid hit an ApolloSpacecraft، كما هو متوقع. يكمن السر في أن البرنامج spaceshipRef.collideWith(asteroidRef);يقوم بما يلي أثناء التشغيل:

  1. spaceshipRefهو مرجع، لذا يبحث C++ عن الطريقة الصحيحة في جدول الدوال الافتراضية. في هذه الحالة، سيستدعي الدالة ApolloSpacecraft::collideWith(Asteroid&).
  2. داخل ApolloSpacecraft::collideWith(Asteroid&)، asteroidيوجد مرجع، لذا asteroid.collideWith(*this)سيؤدي ذلك إلى بحث آخر في جدول الدوال الافتراضية . في هذه الحالة، asteroidيوجد مرجع إلى ، ExplodingAsteroidلذا ExplodingAsteroid::collideWith(ApolloSpacecraft&)سيتم استدعاء .

سي شارب

في لغة C# ، عند استدعاء دالة من نوع مثيل تقبل وسيطًا، يمكن تحقيق إرسال متعدد دون استخدام نمط الزائر. يتم ذلك باستخدام تعدد الأشكال التقليدي مع تحويل الوسيط إلى نوع ديناميكي . [ 3 ] سيختار الرابط في وقت التشغيل التحميل الزائد المناسب للدالة أثناء التشغيل. سيأخذ هذا القرار في الاعتبار نوع مثيل الكائن في وقت التشغيل (تعدد الأشكال) بالإضافة إلى نوع الوسيط في وقت التشغيل.

إيفل

تُتيح لغة برمجة إيفل تطبيق مفهوم الوكلاء على مشكلة الإرسال المزدوج. ويُطبّق المثال التالي بنية لغة الوكلاء على مشكلة الإرسال المزدوج.

لنفترض وجود مجال مشكلة يتضمن أشكالًا مختلفة من SHAPE وأسطح رسم يمكن رسم SHAPE عليها. يعرف كل من SHAPE وSURFACE دالة تُسمى "draw" في حد ذاته، لكنهما لا يعرفانها في بعضهما البعض. نريد أن تتفاعل كائنات من النوعين مع بعضها البعض بشكل متبادل في عملية إرسال مزدوجة باستخدام نمط الزائر.

يكمن التحدي في جعل سطح متعدد الأشكال يرسم شكلاً متعدد الأشكال على نفسه.

الناتج

يُظهر المثال التالي نتائج تمرير كائنين من نوع SURFACE بشكل متعدد الأشكال عبر قائمة من كائنات SHAPE متعددة الأشكال. لا يُدرك نمط كود الزائر سوى نوعي SHAPE وSURFACE بشكل عام، وليس نوع أي منهما تحديدًا. بدلًا من ذلك، يعتمد الكود على تعدد الأشكال أثناء التشغيل وآليات العوامل لتحقيق علاقة تباين مشتركة مرنة للغاية بين هذين النوعين المؤجلين وفروعهما.

ارسم شكلًا متعدد الأضلاع أحمر على برنامج ETCHASKETCH ارسم مضلعًا أحمر على جدار الكتابة على الجدران ارسم مستطيلاً رمادياً على برنامج ECHASKETCH ارسم مستطيلاً رمادي اللون على جدار الكتابة على الجدران ارسم شكلاً رباعياً أخضر على برنامج ETCHASKETCH ارسم شكلاً رباعي الأضلاع أخضر على جدار الكتابة على الجدران ارسم متوازي أضلاع أزرق على برنامج ETCHASKETCH ارسم متوازي أضلاع أزرق على جدار الكتابة على الجدران ارسم مضلعًا أصفر اللون على برنامج ETCHASKETCH ارسم مضلعًا أصفر على جدار الكتابة على الجدران ارسم مستطيلاً بنفسجي اللون على برنامج ETCHASKETCH ارسم مستطيلاً بنفسجي اللون على جدار الكتابة على الجدران

يثبت

قبل النظر إلى SHAPE أو SURFACE، نحتاج إلى فحص الاستخدام المنفصل عالي المستوى للإرسال المزدوج لدينا.

نمط الزائر

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

في المثال أدناه، نقوم بإنشاء قائمة من كائنات SHAPE متعددة الأشكال، ونزور كل منها باستخدام SURFACE متعدد الأشكال، ونطلب رسم SHAPE على SURFACE.

يصنع-- اطبع الأشكال على الأسطح.محليl_shapes : ARRAYED_LIST [ SHAPE ]l_surfaces : ARRAYED_LIST [ SURFACE ]يفعلإنشاء l_shapes . إنشاء ( 6 )l_shapes . extend ( create { POLYGON }. make_with_color ( "red" ))l_shapes . extend ( create { RECTANGLE }. make_with_color ( "grey" ))l_shapes . extend ( create { QUADRILATERAL }. make_with_color ( "green" ))l_shapes . extend ( create { PARALLELOGRAM }. make_with_color ( "blue" ))l_shapes . extend ( create { POLYGON }. make_with_color ( "yellow" ))l_shapes.extend ( create { RECTANGLE } .make_with_color ( " purple" ) )إنشاء l_surfaces . إنشاء ( 2 )l_surfaces . extend ( create { ETCHASKETCH }. make )l_surfaces . extend ( create { GRAFFITI_WALL }. make )حلقة عبر l_shapes كـ ic_shapesحلقة عبر l_surfaces كـ ic_surfacesic_surfaces.item.drawing_agent ( ic_shapes.item.drawing_data_agent )نهايةنهايةنهاية

نبدأ بإنشاء مجموعة من كائنات SHAPE وSURFACE. ثم نمرّ على إحدى القائمتين (SHAPE)، مما يسمح لعناصر القائمة الأخرى (SURFACE) بزيارة كلٍّ منها بدورها. في مثال الكود أعلاه، تزور كائنات SURFACE كائنات SHAPE.

يقوم الكود باستدعاء متعدد الأشكال للدالة {SURFACE}.draw بشكل غير مباشر عبر `drawing_agent`، وهو الاستدعاء الأول (التنفيذ) لنمط التنفيذ المزدوج. ويمرر الكود وكيلاً غير مباشر ومتعدد الأشكال (`drawing_data_agent`)، مما يسمح لكود الزائر بمعرفة أمرين فقط:

  • ما هو عامل الرسم الخاص بالسطح (على سبيل المثال al_surface.drawing_agent في السطر رقم 21)؟
  • ما هو وكيل بيانات الرسم للشكل (على سبيل المثال al_shape.drawing_data_agent في السطر رقم 21)؟

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

سطح

يوجد داخل الاستدعاء متعدد الأشكال لـ {SURFACE}.draw استدعاء لعامل، والذي يصبح الاستدعاء متعدد الأشكال الثاني أو الإرسال في نمط الإرسال المزدوج.

الفصل الدراسي المؤجلسطحالميزة { لا شيء } -- التهيئةيصنع-- تهيئة التيار الحالي.يفعلdrawing_agent := agent drawنهايةميزة -- الوصولdrawing_agent : PROCEDURE [ ANY , TUPLE [ STRING , STRING ]]-- عامل سحب التيار.الميزة { لا شيء } -- التنفيذارسم ( a_data_agent : دالة [ أي ، مجموعة ، مجموعة [ الاسم ، اللون : سلسلة نصية ]] )-- ارسم `a_shape` على Current.محليl_result : TUPLE [ name , color : STRING ]يفعلl_result := a_data_agent ( Void )print ( "ارسم " + l_result.color + " " + l_result.name + " على " + type + " % N" )نهايةالنوع : سلسلة نصية-- اسم النوع الحالي.نهاية مؤجلةنهاية

الوسيط agent في السطر رقم 19 والاستدعاء في السطر رقم 24 كلاهما متعدد الأشكال ومنفصل. العامل منفصل لأن خاصية {SURFACE}.draw لا تعرف على أي فئة يستند إليها `a_data_agent`. لا توجد طريقة لمعرفة الفئة التي اشتُق منها العامل، لذا ليس بالضرورة أن يكون من الفئة SHAPE أو أي من فئاتها الفرعية. هذه ميزة واضحة لعوامل Eiffel مقارنةً بالوراثة الأحادية والربط الديناميكي ومتعدد الأشكال في اللغات الأخرى.

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

وأخيرًا، لاحظ كيف يتم تصدير ميزة "drawing_agent" فقط إلى أي عميل! هذا يعني أن رمز نمط الزائر (وهو العميل الوحيد لهذه الفئة) يحتاج فقط إلى معرفة الوكيل لإنجاز مهمته (على سبيل المثال، استخدام الوكيل كميزة مطبقة على الكائنات التي تمت زيارتها).

شكل

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

بالإضافة إلى ذلك، يُرجى ملاحظة أن برنامج SHAPE لا يُوفر سوى `drawing_data_agent` كميزة مُصدّرة بالكامل لأي عميل. لذا، فإن الطريقة الوحيدة للتفاعل مع برنامج SHAPE، بخلاف عملية الإنشاء، هي من خلال وظائف `drawing_data_agent`، التي يستخدمها أي عميل لجمع بيانات الرسم لبرنامج SHAPE بشكل غير مباشر ومتعدد الأشكال!

الفصل الدراسي المؤجلشكلالميزة { لا شيء } -- التهيئةmake_with_color ( a_color : like color )-- قم بإنشاء باستخدام `a_color` كـ `color`.يفعلاللون := لون_أdrawing_data_agent := agent drawing_ataيضمنcolor_set : color . same_string ( a_color )نهايةميزة -- الوصولdrawing_data_agent : FUNCTION [ ANY , TUPLE , like drawing_data ]-- وكيل البيانات للرسم.الميزة { لا شيء } -- التنفيذبيانات_الرسم : مجموعة [ الاسم : مثل الاسم ؛ اللون : مثل اللون ]-- البيانات المطلوبة لرسم التيار.يفعلالنتيجة := [ الاسم ، اللون ]نهايةالاسم : نص-- اسم الكائن الحالي.نهاية مؤجلةاللون : سلسلة-- لون التيار.نهاية

مثال كلاسيكي لسفينة فضائية

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

تغير سفينة الفضاء إنتربرايز موقعها من A-001 إلى A-002. تتخذ سفينة الفضاء إنتربرايز إجراءً مراوغًا، متجنبة الكويكب "روغ 1"! تغير سفينة الفضاء إنتربرايز موقعها من A-002 إلى A-003. تتخذ سفينة الفضاء إنتربرايز إجراءً مراوغًا، متجنبة الكويكب "روغ 2"! تقوم سفينة الفضاء إنتربرايز بنقل فريق علمي إلى سفينة الفضاء إكسلسيور أثناء مرورها! تغير سفينة الفضاء إنتربرايز موقعها من A-003 إلى A-004. تغير سفينة الفضاء إكسلسيور موقعها من A-003 إلى A-005. تتخذ سفينة الفضاء إنتربرايز إجراءً مراوغًا، متجنبة الكويكب "روغ 3"! تقع سفينة الفضاء إكسلسيور بالقرب من محطة الفضاء ديب سبيس 9 وهي قابلة للالتحام. تغير سفينة الفضاء إنتربرايز موقعها من A-004 إلى A-005. تقوم سفينة الفضاء إنتربرايز بنقل فريق علمي إلى سفينة الفضاء إكسلسيور أثناء مرورها! تقع سفينة الفضاء إنتربرايز بالقرب من محطة الفضاء ديب سبيس 9 ويمكن الالتحام بها. 
زائر

يحتوي الزائر في مثال المركبة الفضائية الكلاسيكي أيضًا على آلية إرسال مزدوجة.

يصنع-- السماح لأجسام المركبات الفضائية بزيارة الكون والتحرك فيه.محليl_universe : ARRAYED_LIST [ SPACE_OBJECT ]l_enterprise ,إل إكسلسيور : سفينة فضائيةيفعلإنشاء l_enterprise.make_with_name ( "Enterprise" , " A-001 " )إنشاء l_excelsior.make_with_name ( "Excelsior" , " A-003 " )إنشاء l_universe.make ( 0 )l_الكون . القوة ( l_enterprise )l_universe.force ( create { ASTEROID } .make_with_name ( "Rogue 1" , "A - 002" ) )l_universe.force ( create { ASTEROID } .make_with_name ( "Rogue 2" , " A-003" ) )l_الكون . القوة ( l_excelsior )l_universe.force ( create { ASTEROID } .make_with_name ( "Rogue 3" , "A-004 " ) )l_universe.force ( create { SPACESTATION } .make_with_name ( "Deep Space 9" , " A - 005" ))قم بزيارة ( l_enterprise , l_universe )l_enterprise.set_position ( "A- 002 " )قم بزيارة ( l_enterprise , l_universe )l_enterprise.set_position ( "A- 003 " )قم بزيارة ( l_enterprise , l_universe )l_enterprise.set_position ( "A- 004 " )l_excelsior.set_position ( "A- 005 " )قم بزيارة ( l_enterprise , l_universe )قم بزيارة ( l_excelsior , l_universe )l_enterprise.set_position ( "A- 005 " )قم بزيارة ( l_enterprise , l_universe )نهايةالميزة { لا شيء } -- التنفيذزيارة ( a_object : SPACE_OBJECT ; a_universe : ARRAYED_LIST [ SPACE_OBJECT ] )-- `a_object' visits `a_universe'.يفعلعبر الكون a_universe كحلقة ic_universeتحقق من المرفق { SPACE_OBJECT } ic_universe . item كـ al_universe_object ثمa_object . encounter_agent . call ( [ al_universe_object . sensor_data_agent ] )نهايةنهايةنهاية

يمكن ملاحظة الإرسال المزدوج في السطر رقم 35، حيث يعمل عاملان غير مباشرين معًا لتوفير استدعاءين متغايرين يعملان بتناغم تام متعدد الأشكال. يحتوي الكائن `a_object` الخاص بميزة `visit` على `encounter_agent` الذي يتم استدعاؤه باستخدام بيانات المستشعر `sensor_data_agent` القادمة من الكائن `al_universe_object`. الجزء الآخر المثير للاهتمام في هذا المثال هو فئة SPACE_OBJECT وميزة `encounter` الخاصة بها.

إجراءات الزائر

الميزات الوحيدة المُصدَّرة من كائن الفضاء (SPACE_OBJECT) هي عوامل معالجة بيانات اللقاء والاستشعار، بالإضافة إلى إمكانية تحديد موقع جديد. عند زيارة أي كائن (المركبة الفضائية) لأي كائن في الكون، تُجمع بيانات الاستشعار وتُمرَّر إلى الكائن الزائر في عامل اللقاء الخاص به. هناك، تُقيَّم بيانات الاستشعار من عامل بيانات الاستشعار (sensor_data_agent) (أي عناصر بيانات مجموعة بيانات الاستشعار التي يُعيدها استعلام sensor_data_agent) مقابل الكائن الحالي، ويُتخذ إجراء بناءً على هذا التقييم (انظر "اللقاء" في كائن الفضاء أدناه). تُصدَّر جميع البيانات الأخرى إلى {NONE}. هذا مشابه لنطاقات Private في لغات C وC++ وJava. تُستخدم البيانات والروتينات، باعتبارها ميزات غير مُصدَّرة، داخليًا فقط بواسطة كل كائن فضاء. أخيرًا، تجدر الإشارة إلى أن استدعاءات "الطباعة" عند اللقاء لا تتضمن معلومات محددة حول الفئات الفرعية المحتملة لكائن الفضاء! الشيء الوحيد الموجود في هذا المستوى من التوريث هو الجوانب العلائقية العامة القائمة كليًا على ما يمكن معرفته من سمات وروتينات كائن الفضاء العام. إن كون مخرجات أمر "الطباعة" منطقية بالنسبة لنا كبشر، استنادًا إلى ما نعرفه أو نتخيله عن سفن الفضاء ومحطات الفضاء والكويكبات، ليس إلا تخطيطًا منطقيًا أو مصادفة. كائن الفضاء غير مبرمج بأي معرفة محددة عن أسلافه.

الفصل الدراسي المؤجلكائن مكانيالميزة { لا شيء } -- التهيئةmake_with_name ( a_name : like name ; a_position : like position )-- تهيئة المتغير الحالي باستخدام `a_name` و `a_position`.يفعلالاسم := اسم_أالموضع := موضع_أsensor_data_agent := agent sensor_ataencounter_agent := agent encounterيضمنname_set : name . same_string ( a_name )position_set : position . same_string ( a_position )نهايةميزة -- الوصولencounter_agent : PROCEDURE [ ANY , TUPLE ]-- وكيل إدارة اللقاءات مع التيار.sensor_data_agent : FUNCTION [ ANY , TUPLE , attached like sensor_data_anchor ]-- وكيل لإعادة بيانات المستشعر الخاصة بالتيار.ميزة -- الإعداداتset_position ( a_position : like position )-- قم بتعيين `position` باستخدام `a_position`.يفعلprint ( type + " " + name + " يغير الموضع من " + position + " إلى " + a_position + ".%N" )الموضع := موضع_أيضمنposition_set : position . same_string ( a_position )نهايةالميزة { لا شيء } -- التنفيذمواجهة ( a_sensor_agent : FUNCTION [ ANY , TUPLE , attached like sensor_data_anchor ] )-- اكتشاف حالة التصادم بين التيار و `a_radar_agent` والإبلاغ عنها.يفعلa_sensor_agent . call ( [ Void ] )تحقق من المرفق { مثل sensor_data_anchor } a_sensor_agent . last_result كـ al_sensor_data ثمإذا لم يكن الاسم مطابقًا لـ ( al_sensor_data.name ) ،إذا كان ( position.same_string ( al_sensor_data.position ) )إذا كان ( al_sensor_data.is_dockable و is_dockable )( is_manned و al_sensor_data.is_manned ) و( is_maneuverable and al_sensor_data . is_not_maneuverable )) ثمprint ( type + " " + name + " قريب من " + al_sensor_data . type + " " +al_sensor_data . name + " and is dockable.%N" )وإلا إذا كان ( is_dockable و al_sensor_data . is_dockable ) و( is_manned و al_sensor_data.is_manned ) و( is_maneuverable and al_sensor_data . is_maneuverable )) ثمprint ( type + " " + name + " ينقل فريقًا علميًا إلى " + al_sensor_data . type + " " +al_sensor_data . name + " as they pass!%N" )وإلا إذا كان ( is_manned و al_sensor_data.is_not_manned )اطبع ( النوع + " " + الاسم + " يتخذ إجراءً مراوغًا، متجنبًا " +al_sensor_data.type + " ` " + al_sensor_data.name + "'!% N " )نهايةنهايةنهايةنهايةنهايةالاسم : نص-- اسم التيار.النوع : سلسلة نصية-- نوع التيار.مؤجلنهايةالموضع : سلسلة نصية-- موضع التيار.قابل للرسو : منطقيهل يمكن ربط المركبة الحالية بمركبة مأهولة أخرى؟مؤجلنهايةis_manned : BOOLEANهل "كارنت" منشأة مأهولة؟مؤجلنهايةقابل للمناورة : منطقيهل يمكن نقل التيار الكهربائي؟مؤجلنهايةبيانات_المستشعر : مرفقة مثل مرساة_بيانات_المستشعربيانات المستشعر الخاصة بالتيار.يفعلالنتيجة := [ الاسم ، النوع ، الموقع ، قابل للرسو ، غير قابل للرسو ، مأهول ، غير مأهول ، قابل للمناورة ، غير قابل للمناورة ]نهايةsensor_data_anchor : detachable TUPLE [ name , type , position : STRING ; is_dockable , is_not_dockable , is_manned , is_not_manned , is_maneuverable , is_not_maneuverable : BOOLEAN ]-- نوع بيانات المستشعر: نقطة ارتكاز التيار.نهاية

هناك ثلاثة أنواع فرعية من SPACE_OBJECT:

جسم فضائي، كويكب، سفينة فضائية ، محطة فضائية

في مثالنا، تُستخدم فئة ASTEROID للعناصر "المارقة"، وSPACESHIP لسفينتي الفضاء، وSPACEESTATION لمحطة الفضاء العميق تسعة. في كل فئة، يكمن التخصص الوحيد في تحديد خاصية "النوع" وبعض خصائص الكائن. يُحدد "الاسم" في إجراء الإنشاء، وكذلك "الموقع". على سبيل المثال: فيما يلي مثال على SPACESHIP.

فصلمركبة فضائيةالميراثكائن مكانييخلقصنع بالاسمالميزة { لا شيء } -- التنفيذالنوع : نص = "سفينة فضائية"-- <السابق>is_dockable : BOOLEAN = True-- <السابق>is_manned : BOOLEAN = True-- <السابق>قابل للمناورة : قيمة منطقية = صحيح-- <السابق>نهاية

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

خاتمة

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

انظر أيضاً

مراجع

  1. تقنية بسيطة للتعامل مع تعدد الأشكال. في وقائع مؤتمر OOPSLA '86، أنظمة ولغات وتطبيقات البرمجة الكائنية، الصفحات 347-349، نوفمبر 1986. نُشرت كإشعارات SIGPLAN، 21(11). ISBN 0-89791-204-7
  2. ↑ كتاب "لغة C++ أكثر فعالية" للمؤلف سكوت مايرز (أديسون-ويسلي، 1996)
  3. "استخدام النوع الديناميكي (دليل برمجة C#)" . شبكة مطوري مايكروسوفت . مايكروسوفت. 30 سبتمبر 2009. تم الاطلاع عليه في 25 مايو 2016. ... يتم حل التحميل الزائد في وقت التشغيل بدلاً من وقت الترجمة إذا كان واحد أو أكثر من الوسائط في استدعاء دالة من النوع الديناميكي ...