البحث في الموقع
المحتوى عن 'liskov substitution principle'.
-
مبدأ آخر من مبادئ التصميم الكائنيّ التوجّه ضمن مبادئ SOLID يُطلق عليه اسم مبدأ ليسكوف للاستبدال Liskov Substitution Principle ويُرمز له اختصارًا بالرمز LSP. سنبدأ هذا المبدأ بشيء من المفاهيم النظريّة. صاحبة هذا المبدأ هي البروفسور باربارا ليسكوف، وقد طرحته أوّل الأمر عام 1987 وكان ينص على ما يلي: قد يبدو الكلام السابق مبهمًا بعض الشيء، يمكننا توضيحه بالشكل التالي: "إذا استطعنا استبدال كل كائن O1 مكان كائن O2 فمن الممكن الجزم بأنّ S هو نوع فرعي (نوع ابن) للنوع T". ولكن في بعض الأحيان رغم أنّ S هو نوع فرعي للنوع T ولكن لا يمكن الاستبدال بين كائناتهما بالصورة الموضّحة قبل قليل وهذا بالطبع أمر غير جيّد. أعادت باربارا ليسكوف بالاشتراك مع جانيت وينغ Jeannette Wing صياغة المبدأ السابق بشكل مختصر أكثر في عام 1994 ليصبح على الشكل التالي: المقصود هنا أنّه إذا كانت خاصيّة أو دالّة (طريقة method) تعمل ضمن كائنات من النوع T، فينبغي أن تعمل أيضًا وبنفس الصورة ضمن كائنات من النوع S بحيث أنّ النوع S هو نوع ابن للنوع T. وهذا هو مبدأ ليسكوف للاستبدال الذي وصفه روبرت مارتن لاحقًا بصورة أكثر عمليّةً في إحدى مقالاته على الشكل التالي: بعد كلّ هذه المقدّمة النظريّة ثقيلة الظلّ بعض الشيء، ينبغي أن نُدعّم المفاهيم النظريّة السابقة ببعض الأمثلة التي توضّحها بالشكل المناسب. واحد من هذه الأمثلة التي تخرق هذا المبدأ هو تمثيل العلاقة بين المستطيل والمربّع بشكل كائنيّ. من البديهي تمامًا أن يكون لدينا صنف اسمه Square (مربّع) يكون صنفًا ابنًا للصنف Rectangle (مستطيل)، فمن الناحية الرياضيّة يُعتبر المربّع حالة خاصّة من المستطيل، فهو مستطيل تساوى فيه بعداه. لنعبّر في البداية عن الصنف Rectangle برمجيًّا بالشكل التالي: class Rectangle { int width; int height; public: int getWidth() { return width; } int getHeight() { return height; } virtual void setWidth(int value) { width = value; } virtual void setHeight(int value) { height = value; } }; وبما أنّ المربّع هو حالة خاصّة من المستطيل كما أسلفنا، فيمكن كتابة الصنف Square مع إعادة تعريف الطريقتين setWidth و setHeight: class Square : public Rectangle { public: void setWidth(int value) { width = value; height = value; } void setHeight(int value) { width = value; height = value; } }; سيضمن هذا التعديل على الطريقتين setWidth و setHeight ضمن الصنف Square أنّ أي كائن (مربّع) ننشئه من الصنف Square ستكون أضلاعه الأربعة متساوية الطول. لننظر الآن إلى الدّالة التالية التي سنستخدمها لتجريب البنية الكائنيّة السابقة: bool test(Rectangle &rectangle) { rectangle.setWidth(2); rectangle.setHeight(3); return rectangle.getWidth() * rectangle.getHeight() == 6; } تقبل الدّالة test تمرير كائن من النوع Rectangle أو كائن من النوع Square، وهذا جائز بالطبع لأنّ Square هو نوع ابن للنوع Rectangle. السؤال هنا هو ماذا سيحدث عند تمرير كائن من النوع Square إلى الدّالة test؟ ستُعيد الدّالة القيمة false رغم أنّ التقييم يجري على مرجع من النوع Rectangle (وسيط الدّالة). المشكلة هنا أنّه رغم أنّ المربّع هو مستطيل من الناحية الرياضيّة إلّا أنّه لا يتشارك السلوك نفسه معه. وهذا يُعتبر خرقًا واضحاً لمبدأ الاستبدال. فالكائنات من النوع Rectangle لا يمكن أن يتمّ استبدالها بكائنات من النوع Square رغم أنّ النوع Square هو نوع ابن للنوع Rectangle، لأنّ ذلك سيؤدّي إلى تغيّر في سلوك الطرائق الموجودة ضمن الصنف Rectangle كما هو واضح. لقد أوضح برتراند ماير هذه المسألة أيضًا على الشكل التالي: الإجراء من النص السابق قد يكون دالة أو طريقة method. الشروط البادئة هي الشروط التي يجب أن تتحقّق كي يتم استدعاء الطريقة وتنفيذها، أمّا الشروط اللّاحقة فهي محقّقة دومًا بعد انتهاء تنفيذ الطريقة. فالّذي قصده برتراند ماير هو أنّه عند إعادة كتابة تابع في صنف ابن يرث من صنف أب، يجب ألّا تكون الشروط البادئة أقوى من الشروط البادئة للطريقة الأصليّة (الموجودة في النوع الأب)، كما يجب ألّا تكون الشروط الّلاحقة أضعف من الشروط الّلاحقة للطريقة الأصليّة. في مسألتنا السابقة (مسألة المربّع والمستطيل)، لم تكن هناك أيّ شروط بادئة، ولكن كان هناك شرط لاحق للطريقة setHeight وهو أنّ هذه الطريقة يجب ألّا تُغيّر العرض width، وهذا الشرط تمّ خرقه (أصبح أضعف) عندما أعدنا تعريف الطريقة setHeight ضمن النوع Square. تُعتبر الوراثة في الحقيقة مفهومًا قويًّا ومحوريًّا في التصميم كائنيّ التوجّه، ولكن من السهل الوقوع في الأخطاء إذا لم نطبّق مبدأ ليسكوف للاستبدال، فقد تجد أنّ تصميمك الكائنيّ جيّد ومقنع، ولكن سرعان ما ستقع في المتاعب إذا لم تراعي هذا المبدأ. ترجمة -وبتصرّف- للمقال Liskov Substitution Principle لصاحبه Radek Pazdera.
- 1 تعليق
-
- 1