المقدمة
الـ SOLID Principles عبارة عن مجموعة من القواعد البسيطة بتساعد المبرمجين على كتابة كود نظيف ومنظم وسهل الفهم والتعديل. تخيل كأنك بتبني بيت، لازم يكون كل جزء فيه له وظيفة واضحة ومكان محدد عشان البيت يبقى قوي ومستقر.
نفس الكلام في البرمجة، الكود لازم يكون منظّم عشان يسهل تطويره وصيانته!
ليه المبادئ دي مهمة؟
- كود نظيف ومنظم: بيسهل فهمه وتعديله
- صيانة أسهل: لو فيه أي مشكلة، هتلاقيها بسرعة وتصلحها
- توسعة أسرع: تقدر تضيف ميزات جديدة بسهولة
- تعاون أفضل بين المبرمجين: كل واحد هيفهم شغله كويس
- كفاءة أعلى: الكود هيشتغل بشكل أسرع وأكثر استقرارًا
مبدأ الـ Liskov Substitution
الصورة بتشرح مبدأ "Liskov Substitution Principle" بطريقة بسيطة باستخدام روبوتات. الفكرة بتقول إن لو عندك كلاس (فئة) وورثت منها كلاس تاني، المفروض الكلاس الجديد يقدر يحل مكان القديم من غير ما يغير في السلوك الأساسي.
في أول جزء (اللي فوق):
- الروبوت الكبير "سام" بيقول: "أنا سام، بعمل قهوة."
- بعدها يظهر روبوت أصغر اسمه "إيدن" بيقول: "أنا إيدن، ابن سام."
- حد بيطلب من "سام" إنه يعمل له قهوة، وسام بيديله القهوة، والراجل بيشكره.
في الجزء اللي تحت:
- على الشمال، حد بيطلب من "إيدن" يعمل قهوة لأن "سام" مش موجود. "إيدن" بدل ما يعمل قهوة، بيديله ماء! فاللي طلب القهوة مستغرب ووشه مستغرب كده.
- على اليمين، نفس السيناريو، حد بيطلب من "إيدن" يعمل قهوة، وإيدن بيعمل كابتشينو وبيقدمه للشخص، والشخص مبسوط وبيقول له شكراً.
الجزء الأحمر اللي على الشمال عليه علامة غلط، وده لأنه "إيدن" مكانش قادر يحل مكان "سام" بشكل صحيح وقدم حاجة مختلفة (ماء بدل القهوة). إنما الجزء الأخضر اللي على اليمين عليه علامة صح، لأن "إيدن" عمل حاجة أحسن (كابتشينو) بس لسه في نطاق اللي اتطلب منه.
مبدأ Liskov Substitution بيقول ببساطة إن لو عندك كلاس وورثت منه كلاس تاني، الكلاس الجديد لازم يقدر يحل مكان القديم من غير ما يبوظ أي حاجة أو يغير في النتائج المتوقعة.
خليني أديك مثال عملي باستخدام مبدأ Liskov Substitution Principle (LSP) مع Car.
Simple Car Example
المبدأ بيقول إنك تقدر تستخدم أي كلاس ابن بدل الكلاس الأب بدون ما يحصل أي مشاكل أو تغييرات في السلوك المتوقع.
المثال بدون تطبيق مبدأ Liskov:
المشكلة:
كسر مبدأ LSP: لما بنستخدم كلاس ElectricCar، بنواجه مشكلة لأن الكلاس ده بيتعامل مع مفهوم "بدء المحرك" اللي مش موجود فعلًا في السيارات الكهربائية. وده بيكسر مبدأ LSP لأنك لو بدلت كلاس PetrolCar بـ ElectricCar، هتحصل على سلوك غير منطقي، وهو محاولة بدء "محرك" غير موجود.
الحل بعد تطبيق مبدأ LSP:
- الخطوة 1: نفصل السلوك اللي مختلف بين الكلاسات ، مش كل السيارات ليها محرك، وده مفهوم مش لازم يكون موجود في كل العربيات. فهنخلي واجهة Car تتعامل مع السلوك الأساسي اللي كل العربيات بتشاركه، زي القيادة.
- الخطوة 2: نضيف واجهة جديدة مخصصة للعربيات اللي ليها محرك.
التحسينات بعد تطبيق المبدأ:
- فصل السلوكيات المختلفة: دلوقتي السيارات اللي ليها محرك بتستخدم واجهة
EngineCar
، واللي مش ليها محرك زي السيارات الكهربائية مش محتاجة الدالةstartEngine
. - تطبيق مبدأ LSP: دلوقتي تقدر تستخدم
ElectricCar
أوPetrolCar
بشكل منطقي من غير ما تحصل مشاكل. كل كلاس بيقوم بسلوكاته الخاصة من غير ما يورث دوال مش محتاجها. - قابلية التوسع: لو أضفت أنواع جديدة من السيارات، تقدر تعملها بطريقة أسهل بدون ما تحصل تعقيدات بسبب توارث دوال مش مطلوبة.
Simple Shape Area Example
افترض عندنا كلاس بيعمل حساب المساحة لأشكال هندسية زي المستطيل والمربع.
الكود اللي كتبته هنا بيكسر مبدأ Liskov Substitution Principle (LSP)، اللي هو جزء من الـ SOLID Principles. المشكلة بتظهر لما بنخلط بين الـ Rectangle والـ Square بطريقة تخلي السلوك بتاع الكود غير منطقي.
المشكلة في الكود:
خليط بين المستطيل والمربع: الـ SquareWithoutLiskov هو كلاس موروث من RectangleWithoutLiskov. ده بيعمل مشاكل لأن المستطيل ليه خواص مختلفة عن المربع.
المربع هو حالة خاصة من المستطيل، بحيث يكون الطول يساوي العرض. لكن هنا، لما تورث المربع من المستطيل وتعدل في width وlength، بتكسر المنطق الخاص بالمستطيل.
الاختلاف في السلوك:
في حالة الـ RectangleWithoutLiskov العادي، لما تحسب المساحة باستخدام width * length، الحساب بيكون سليم.
لكن في حالة الـ SquareWithoutLiskov، بيتم تعديل كلًا من width وlength ليكونوا نفس القيمة، وده بيؤدي لسلوك غير متوقع لو حاولت تستخدم الكلاس كـ Rectangle. وده اللي بنشوفه في السطر اللي بنعرف فيه:
هنا إنت عرّفت كائن من SquareWithoutLiskov بس إديته length مختلف عن width. لكن بعد ما تديله width جديد (5)، القيمة بتاعة length برضه بتتغير وتبقى نفس قيمة width، وده مش منطقي في حالة إنك عايز تتعامل مع الكائن كـ Rectangle.
السلوك الحالي:
هنا الحسابات متلغبطتش بسبب إن المربع فرض إنه يساوي الطول بالعرض وده كسر مبدأ LSP اللي بيقول إن الكلاس الأب لازم يكون ممكن تستبدله بالكلاس الابن من غير مشاكل.
لما نطبّق مبدأ Liskov Substitution، مش المفروض نخلّي Square يرث من Rectangle لأن المربع ليه قواعد مختلفة عن المستطيل. بدل ما نستخدم الوراثة، ممكن نستخدم (interface) أو (abstract class) بتحدد سلوك حساب المساحة لأي شكل هندسي.
الكود المحسن:
في الختام
بكده نكون شوفنا مع بعض أهمية مبدأ الـ Liskov Substitution في التوسع وفصل السلوكيات المختلفة وكمان شوفنا قد ايه بيتميز بالمرونة والصيانة الأسهل.
ده كان تالت مبدأ من الـ SOLID Principles ولسه فيه مبادئ تانية هنتكلم عنها باذن الله.
Discussion