التصنيف الفرعي السلوكي

في البرمجة كائنية التوجه ، يُعدّ التنميط الفرعي السلوكي مبدأً ينص على أن الفئات الفرعية يجب أن تُلبي توقعات العملاء الذين يصلون إلى كائنات الفئة الفرعية من خلال مراجع من نوع الفئة الأصلية، ليس فقط فيما يتعلق بالسلامة النحوية (مثل عدم وجود أخطاء "الطريقة غير موجودة")، بل أيضًا فيما يتعلق بصحة السلوك. وبالتحديد، يجب أن تكون الخصائص التي يمكن للعملاء إثباتها باستخدام مواصفات النوع المفترض للكائن صحيحة حتى لو كان الكائن في الواقع عضوًا في نوع فرعي من ذلك النوع. [ 1 ]
على سبيل المثال، لنفترض وجود نوعين: Stack و Queue ، وكلاهما يحتوي على دالة put لإضافة عنصر ودالة get لحذفه. لنفترض أن التوثيق الخاص بهذين النوعين ينص على أن دوال النوع Stack يجب أن تعمل كما هو متوقع للمكدسات (أي أنها تتبع سلوك LIFO )، وأن دوال النوع Queue يجب أن تعمل كما هو متوقع للطوابير (أي أنها تتبع سلوك FIFO ). لنفترض الآن أن النوع Stack تم تعريفه كفئة فرعية من النوع Queue. تتجاهل معظم مُجمِّعات لغات البرمجة التوثيق وتُجري فقط الفحوصات اللازمة للحفاظ على سلامة النوع . بما أن النوع Stack يوفر دالة تحمل نفس الاسم والتوقيع لكل دالة من النوع Queue، فإن هذا الفحص سينجح. مع ذلك، فإن العملاء الذين يصلون إلى كائن Stack من خلال مرجع من النوع Queue، سيتوقعون، بناءً على توثيق Queue، سلوك FIFO، لكنهم سيلاحظون سلوك LIFO، مما يُبطل براهين صحة هؤلاء العملاء، وقد يؤدي إلى سلوك غير صحيح للبرنامج ككل.
هذا المثال ينتهك التنميط الفرعي السلوكي لأن النوع Stack ليس نوعًا فرعيًا سلوكيًا من النوع Queue: ليس الأمر أن السلوك الموصوف في وثائق النوع Stack (أي سلوك LIFO) يتوافق مع وثائق النوع Queue (الذي يتطلب سلوك FIFO).
في المقابل، فإن البرنامج الذي يكون فيه كل من Stack وQueue فئتين فرعيتين من نوع Bag، والذي تقتصر وظيفة get فيه على إزالة عنصر ما ، يُلبي متطلبات التنميط الفرعي السلوكي، ويسمح للمستخدمين بالتحقق من صحة البرنامج بناءً على الأنواع المفترضة للكائنات التي يتفاعلون معها. في الواقع، أي كائن يُلبي مواصفات Stack أو Queue يُلبي أيضًا مواصفات Bag.
من المهم التأكيد على أن كون النوع S نوعًا فرعيًا سلوكيًا للنوع T يعتمد فقط على مواصفات النوع T (أي توثيقه )؛ أما تنفيذ النوع T، إن وُجد، فهو غير ذي صلة تمامًا بهذا السؤال. في الواقع، قد لا يكون للنوع T تنفيذٌ أصلاً؛ فقد يكون مجرد فئة مجردة. كمثال آخر، يُعد النوع Stack المذكور أعلاه نوعًا فرعيًا سلوكيًا للنوع Bag حتى لو كان تنفيذ النوع Bag يُظهر سلوك FIFO: المهم هو أن مواصفات النوع Bag لا تُحدد العنصر الذي تُزيله الدالة get . هذا يعني أيضًا أنه لا يمكن مناقشة التصنيف الفرعي السلوكي إلا فيما يتعلق بمواصفات (سلوكية) محددة لكل نوع معني، وأنه إذا لم تكن للأنواع المعنية مواصفات سلوكية محددة جيدًا، فلا يمكن مناقشة التصنيف الفرعي السلوكي بشكلٍ ذي معنى.
التحقق من التصنيف الفرعي السلوكي
يُعتبر النوع S نوعًا فرعيًا سلوكيًا من النوع T إذا كان كل سلوك مسموح به في مواصفات S مسموحًا به أيضًا في مواصفات T. وهذا يتطلب، على وجه الخصوص، أنه بالنسبة لكل طريقة M من T، فإن مواصفات M في S أقوى من تلك الموجودة في T.
تكون مواصفات الطريقة المُعطاة بشرط مسبق P <sub> s </sub> وشرط لاحق Q <sub> s</sub> أقوى من تلك المُعطاة بشرط مسبق P<sub> t </sub> وشرط لاحق Q<sub> t </sub> (بصورة رسمية: (P <sub>s</sub> , Q <sub> s </sub> ) ⇒ (P <sub>t </sub> , Q <sub> t </sub> )) إذا كان P <sub>s </sub> أضعف من P <sub> t </sub> (أي أن P <sub> t </sub> يستلزم P<sub>s</sub>) وكان Q <sub> s </sub> أقوى من Q <sub>t</sub> (أي أن Q <sub>s </sub> يستلزم Q<sub> t</sub> ). أي أن تقوية مواصفات الطريقة تتم بتقوية الشرط اللاحق وإضعاف الشرط المسبق. في الواقع، تكون مواصفات الطريقة أقوى إذا فرضت قيودًا أكثر تحديدًا على المخرجات للمدخلات التي كانت مدعومة بالفعل، أو إذا تطلبت دعم المزيد من المدخلات.
على سبيل المثال، لنفترض المواصفات (الضعيفة جدًا) لدالة تحسب القيمة المطلقة لمتغير x ، والتي تحدد شرطًا مسبقًا 0 ≤ x وشرطًا لاحقًا 0 ≤ result. تنص هذه المواصفات على أن الدالة لا تحتاج إلى دعم القيم السالبة لـ x ، ويكفيها فقط ضمان أن تكون النتيجة غير سالبة. هناك طريقتان محتملتان لتقوية هذه المواصفات: إما بتقوية الشرط اللاحق ليصبح result = |x|، أي أن النتيجة تساوي القيمة المطلقة لـ x، أو بتخفيف الشرط المسبق ليصبح "true"، أي أنه يجب دعم جميع قيم x . بالطبع، يمكننا أيضًا الجمع بين الطريقتين في مواصفات تنص على أن النتيجة يجب أن تساوي القيمة المطلقة لـ x ، لأي قيمة لـ x .
لاحظ، مع ذلك، أنه من الممكن تقوية مواصفة ((P s , Q s ) ⇒ (P t , Q t )) دون تقوية الشرط اللاحق (Q s ⇏ Q t ). [ 2 ] [ 3 ] لنفترض مواصفة لطريقة القيمة المطلقة تحدد شرطًا مسبقًا 0 ≤ x ونتيجة شرط لاحق = x. إن المواصفة التي تحدد شرطًا مسبقًا "صحيحًا" ونتيجة شرط لاحق = |x| تقوي هذه المواصفة، على الرغم من أن نتيجة الشرط اللاحق = |x| لا تقوي (أو تضعف) نتيجة الشرط اللاحق = x. الشرط الضروري لكي تكون المواصفة ذات الشرط المسبق P s والشرط اللاحق Q s أقوى من المواصفة ذات الشرط المسبق P t والشرط اللاحق Q t هو أن تكون P s أضعف من P t وأن تكون "Q s أو ليس P s " أقوى من "Q t أو ليس P t ". في الواقع، فإن عبارة "النتيجة = |x| أو خطأ" تعزز عبارة "النتيجة = x أو x < 0".
"قابلية الاستبدال"
في كلمة رئيسية مؤثرة [ 4 ] حول تجريد البيانات وتسلسل الفئات في مؤتمر أبحاث لغات البرمجة OOPSLA 1987، قالت باربرا ليسكوف ما يلي: "المطلوب هنا هو شيء مثل خاصية الاستبدال التالية: إذا كان لكل كائنيوجد كائن من النوع Sمن النوع T بحيث يكون سلوك P غير متغير بالنسبة لجميع البرامج P المعرفة بدلالة T عندمايتم استبدالها بـإذاً، S هو نوع فرعي من T." عُرف هذا التوصيف منذ ذلك الحين على نطاق واسع باسم مبدأ استبدال ليسكوف (LSP). مع ذلك، وللأسف، ينطوي على عدة مشكلات. أولاً، في صيغته الأصلية، هو قوي للغاية: نادرًا ما نرغب في أن يكون سلوك فئة فرعية مطابقًا لسلوك فئتها الأصلية؛ غالبًا ما يتم استبدال كائن من فئة فرعية بكائن من فئة أصلية بقصد تغيير سلوك البرنامج، وإن كان ذلك، إذا تم احترام التنميط الفرعي السلوكي، بطريقة تحافظ على خصائص البرنامج المرغوبة. ثانيًا، لا يذكر هذا المبدأ المواصفات ، مما يُؤدي إلى قراءة خاطئة عند مقارنة تنفيذ النوع S بتنفيذ النوع T. وهذا إشكالي لعدة أسباب، منها أنه لا يدعم الحالة الشائعة حيث يكون T مجردًا وليس له تنفيذ. ثالثًا، والأهم من ذلك، في سياق البرمجة الإجرائية الموجهة للكائنات ، من الصعب تحديد معنى التكميم الشامل أو الوجودي على كائنات من نوع معين، أو استبدال كائن بآخر. [ 3 ] في المثال في المثال أعلاه، نحن لا نستبدل كائن Stack بكائن Bag، بل نستخدم ببساطة كائن Stack ككائن Bag.
في مقابلة أجريت عام 2016، أوضحت ليسكوف نفسها أن ما قدمته في كلمتها الرئيسية كان "قاعدة غير رسمية"، وأن جانيت وينغ اقترحت لاحقًا أن "تحاولا فهم معناها بدقة"، مما أدى إلى نشرهما المشترك [ 1 ] حول التصنيف الفرعي السلوكي، وأنه "يُطلق عليه تقنيًا اسم التصنيف الفرعي السلوكي". [ 5 ] خلال المقابلة، لم تستخدم مصطلحات الاستبدال لمناقشة المفاهيم.
ملحوظات
- 1 2 ليسكوف، باربرا؛ وينغ، جانيت (1994-11-01). "مفهوم سلوكي للتصنيف الفرعي" . معاملات ACM في لغات البرمجة والأنظمة . 16 (6): 1811-1841 . doi : 10.1145/197320.197383 .
- ↑ باركنسون، ماثيو ج. (2005). الاستدلال المحلي للغة جافا (ملف PDF) (أطروحة دكتوراه). جامعة كامبريدج.
- 1 2 ليفنز، غاري ت.؛ ناومان، ديفيد أ. (أغسطس 2015). "التصنيف الفرعي السلوكي، ووراثة المواصفات، والاستدلال المعياري" . معاملات ACM في لغات البرمجة والأنظمة . 37 (4). doi : 10.1145/2766446 .
- ↑ ليسكوف، ب. (مايو 1988). "الكلمة الرئيسية - تجريد البيانات والتسلسل الهرمي" . إشعارات ACM SIGPLAN . 23 (5): 17-34 . doi : 10.1145/62139.62141 .
- ↑ فان فليك، توم (20 أبريل 2016). مقابلة مع باربرا ليسكوف . ACM. مؤرشفة من الأصل بتاريخ 21 ديسمبر 2021.
مراجع
- باركنسون، ماثيو جيه؛ بيرمان، جافين إم. (يناير 2008). "منطق الفصل، والتجريد، والوراثة". إشعارات ACM SIGPLAN . 43 (1): 75-86 . doi : 10.1145/1328897.1328451 .
- البرمجة الكائنية التوجه
