المقدمة

الـ SOLID Principles عبارة عن مجموعة من القواعد البسيطة بتساعد المبرمجين على كتابة كود نظيف ومنظم وسهل الفهم والتعديل. تخيل كأنك بتبني بيت، لازم يكون كل جزء فيه له وظيفة واضحة ومكان محدد عشان البيت يبقى قوي ومستقر.

نفس الكلام في البرمجة، الكود لازم يكون منظّم عشان يسهل تطويره وصيانته!

ليه المبادئ دي مهمة؟ 

  1. كود نظيف ومنظم: بيسهل فهمه وتعديله 
  2. صيانة أسهل: لو فيه أي مشكلة، هتلاقيها بسرعة وتصلحها 
  3. توسعة أسرع: تقدر تضيف ميزات جديدة بسهولة 
  4. تعاون أفضل بين المبرمجين: كل واحد هيفهم شغله كويس 
  5. كفاءة أعلى: الكود هيشتغل بشكل أسرع وأكثر استقرارًا 
💡
 باختصار هي أدوات أساسية للمبرمج المحترف عشان يقدر يبني سوفت وير مرن و سهل التعديل عليه في المستقبل!
82ea0602a13c7ae723f4fabb4c971b46.png
SOLID Principles 101 - Single Responsibility
الـ SOLID Principles عبارة عن مجموعة من القواعد البسيطة بتساعد المبرمجين على كتابة كود نظيف ومنظم وسهل الفهم والتعديل. تخيل كأنك بتبني بيت، لازم يكون كل جزء فيه له وظيفة واضحة ومكان محدد عشان البيت يبقى قوي ومستقر.

Single Responsibility

SOLID Principles 101 - Open Close
تخيل إنك بتبني بيت ، البيت ده زي برنامج، والأجزاء بتاعته زي الغرف والحمامات والمطبخ. دلوقتي، لو عايز تزود غرفة جديدة، هتضيفها للبيت من غير ما تخرب الغرف اللي موجودة. بس مش هتروح تغير في شكل الغرف القديمة وتبوظ الديكور بتاعها، صح؟

Open Close

SOLID Principles 101 - Liskov Substitution
الفكرة بتقول إن لو عندك كلاس (فئة) وورثت منها كلاس تاني، المفروض الكلاس الجديد يقدر يحل مكان القديم من غير ما يغير في السلوك الأساسي.

Liskov Substitution


مبدأ الـ Liskov Substitution


مبدأ الـ Interface Segregation

💡
ISP splits interfaces that are very large into smaller and more specific ones so that clients will only have to know about the methods that are of interest to them.
https://miro.medium.com/v2/resize:fit:1625/1*2hmyR9L43Vm64MYxj4Y89w.png
Interface Segregation

الصورة بتوضح مبدأ Interface Segregation Principle، وده واحد من المبادئ الخمسة بتوع (SOLID). الفكرة إنك ما تجبرش كائن (زي الروبوتات في الصورة) إنه يستخدم حاجات هو مش محتاجها.

الجزء الأيسر (مخالفة المبدأ):

الروبوت مطلوب منه يعمل مجموعة تمارين فيها حاجة هو مش يقدر يعملها (wiggle antennas)، فبيقول: "Oops! But I don’t have antennas" أو "يا نهار، بس أنا معنديش antennas ". هنا في مشكلة لأن الروبوت مضطر ينفذ حاجة مش على مقاسه، وده بيخالف مبدأ تقسيم الواجهات.

الجزء الأيمن (اتباع المبدأ):

التمارين اتقسمت حسب إمكانيات الروبوتات. كل روبوت بيعمل التمرين اللي يناسبه، يعني مثلاً اللي يقدر يلف يعمل تمرين لف، واللي عنده antennas. الروبوت هنا مبسوط وبيقول: "Awesome!"، وده معناه إن النظام دلوقتي بيتبع مبدأ تقسيم الواجهات لأن كل روبوت بيتعامل مع الحاجة اللي تخصه بس.

باختصار، المبدأ ده بيقول إنك تعمل واجهات صغيرة ومحددة عشان كل روبوت (أو كائن) يستخدم اللي هو محتاجه بالظبط، من غير ما يتلخبط بحاجات هو مش محتاجها.


Simple Robot Example

abstract class Robot {
  void spinAround(); // يدور حول نفسه
  void rotateArms(); // يدور بأذرعه
  void wiggleAntennas(); // يهز هوائياته
}

Robot

class ArmRobot implements Robot {
  @override
  void spinAround() {
    print('Robot spinning around');
  }

  @override
  void rotateArms() {
    print('Robot rotating arms');
  }

  @override
  void wiggleAntennas() {
    throw Exception('This robot has no antennas');
  }
}

ArmRobot

void main() {
  ArmRobot robot = ArmRobot();
  robot.spinAround();
  robot.rotateArms();
  robot.wiggleAntennas(); // سيؤدي إلى خطأ لأن هذا الروبوت ليس لديه هوائيات
}

Main

في المثال ده: عندنا واجهة اسمها Robot فيها كل الوظائف، حتى اللي الروبوتات مش بتحتاجها.

الروبوت اللي عنده أذرع بس (ArmRobot) لازم ينفذ كل الوظائف، حتى الوظيفة اللي ملهاش علاقة بيها زي wiggleAntennas، وده مخالف للمبدأ لأن الكائن مجبر على استخدام حاجة مش محتاجها.

الكود بعد تطبيق المبدأ:

abstract class Spinnable {
  void spinAround();
}
abstract class ArmMovable {
  void rotateArms();
}
abstract class AntennaWiggler {
  void wiggleAntennas();
}
class ArmRobot implements Spinnable, ArmMovable {
  @override
  void spinAround() {
    print('Robot spinning around');
  }

  @override
  void rotateArms() {
    print('Robot rotating arms');
  }
}
class AntennaRobot implements Spinnable, AntennaWiggler {
  @override
  void spinAround() {
    print('Robot spinning around');
  }

  @override
  void wiggleAntennas() {
    print('Robot wiggling antennas');
  }
}
void main() {
  ArmRobot armRobot = ArmRobot();
  armRobot.spinAround();
  armRobot.rotateArms();

  AntennaRobot antennaRobot = AntennaRobot();
  antennaRobot.spinAround();
  antennaRobot.wiggleAntennas();
}

في المثال ده: قسمنا الواجهات لوظائف صغيرة (Spinnable، ArmMovable، و AntennaWiggler).
كل روبوت بينفذ الواجهة اللي تخص إمكانياته بس، فالروبوت اللي عنده أذرع بينفذ الواجهات اللي فيها وظائف ليه علاقة بالأذرع، والروبوت اللي عنده هوائيات بينفذ الوظائف اللي تخص الهوائيات.

النتيجة:
كل روبوت بيستخدم بس الوظائف اللي هو محتاجها، وده تطبيق صحيح لمبدأ Interface Segregation Principle.


Simple Printer Example

abstract class Printer {
  void printDocument();
  void scanDocument();
  void print3DObject();
}
class NormalPrinter implements Printer {
  @override
  void printDocument() {
    print('Printing document...');
  }

  @override
  void scanDocument() {
    print('Scanning document...');
  }

  @override
  void print3DObject() {
    throw Exception('This printer cannot print 3D objects');
  }
}

في المثال ده: الواجهة Printer فيها كل الوظائف، حتى لو الطابعة مش بتدعمهم.

الطابعة العادية (NormalPrinter) مجبرة إنها تنفذ وظيفة print3DObject() رغم إنها مش بتدعم الطباعة ثلاثية الأبعاد. وده بيخالف مبدأ ISP لأن الطابعة بتتعامل مع حاجات مش هتستخدمها.


abstract class Printable {
  void printDocument();
}
abstract class Scannable {
  void scanDocument();
}
abstract class Print3D {
  void print3DObject();
}
class NormalPrinter implements Printable, Scannable {
  @override
  void printDocument() {
    print('Printing document...');
  }

  @override
  void scanDocument() {
    print('Scanning document...');
  }
}
class ThreeDPrinter implements Printable, Print3D {
  @override
  void printDocument() {
    print('Printing document...');
  }

  @override
  void print3DObject() {
    print('Printing 3D object...');
  }
}
void main() {
  NormalPrinter normalPrinter = NormalPrinter();
  normalPrinter.printDocument();
  normalPrinter.scanDocument();

  ThreeDPrinter threeDPrinter = ThreeDPrinter();
  threeDPrinter.printDocument();
  threeDPrinter.print3DObject();
}

في المثال ده: قسمنا الواجهات بناءً على القدرات (Printable، Scannable، و Print3D).

الطابعة العادية (NormalPrinter): بتنفذ الواجهات اللي بتناسب إمكانياتها زي الطباعة والمسح الضوئي.

الطابعة الثلاثية الأبعاد (ThreeDPrinter): بتنفذ الواجهات اللي بتخص الطباعة وثلاثية الأبعاد بس.

النتيجة: كل طابعة بقت بتتعامل مع الوظائف اللي تخصها بس، وده بيحقق مبدأ Interface Segregation Principle بشكل صحيح.