المقدمة

الـ 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

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

Interface Segregation


مبدأ الـ Liskov Substitution


مبدأ الـ Dependency Inversion

💡
High-level modules should not depend on low-level modules. Both should depend on the abstraction.

مبدأ Dependency Inversion Principle (DIP): هو أحد المبادئ الخمسة لـ SOLID لتصميم البرمجيات الجيدة. المبدأ ينص على أن:

يجب أن تعتمد (High-Level Modules) على (Abstractions) وليس على (Low-Level Modules) ، الـ Abstractions يجب ألا تعتمد على التفاصيل، بل التفاصيل هي التي تعتمد على Abstractions.

أنا مش فاهم حاجة يعني ايه High-Level Modules و Low-Level Modules و يعني ايه details

المقصود بـ High-Level Modules و Low-Level Modules:

  1. الـ High-Level Modules:
    دي الوحدات أو الطبقات اللي بتحتوي على المنطق الأساسي أو الأهداف الرئيسية للتطبيق. بمعنى آخر، هي الوحدات اللي بتتعامل مع المفاهيم العامة وتنفذ المهام الكبيرة والمهمة.
    مثال: في تطبيق لإدارة السيارات، الوحدة اللي بتتعامل مع "إدارة المستخدمين" أو "إدارة السيارات" تعتبر وحدة عالية المستوى لأنها بتنفذ مهام كبيرة تخص التطبيق.
  2. الـ Low-Level Modules:
    دي الوحدات اللي بتنفذ التفاصيل التقنية الصغيرة أو اللي بتقوم بوظائف معينة ومحددة، مثل التعامل مع الأجهزة أو قواعد البيانات أو المدخلات والمخرجات.
    مثال: في نفس التطبيق لإدارة السيارات، وحدة اللي بتتعامل مع الاتصال بقواعد البيانات أو قراءة الملفات تعتبر وحدة منخفضة المستوى لأنها بتقوم بتفاصيل صغيرة لكن مهمة لعمل التطبيق.

الفكرة الأساسية: الوحدات عالية المستوى هي اللي بتحدد ما الذي يحتاج التطبيق لفعله، بينما الوحدات منخفضة المستوى هي اللي بتحدد كيفية تنفيذ تلك المهام.

مبدأ Dependency Inversion: المبدأ بيقول إن الوحدات عالية المستوى ما تعتمدش مباشرة على الوحدات منخفضة المستوى، بل كلهم لازم يعتمدوا على التجريدات.

مثال:
لو عندك تطبيق لإرسال الإشعارات:

الـ High-Level Module هي المسؤولة عن اتخاذ القرار بإرسال الإشعار (مثلاً عند تسجيل مستخدم جديد).

بينما الـ Low-Level Module هي المسؤولة عن التفاصيل التقنية لإرسال الإشعار (مثلاً إرسال إشعار بالبريد الإلكتروني أو رسالة نصية).

بدون مبدأ Dependency Inversion، High-Level Module هتكون مرتبطة بشكل مباشر بالطريقة المستخدمة لإرسال الإشعار (مثل البريد الإلكتروني). لكن لو استخدمنا المبدأ، الـ High-Level Module هتعتمد على (interface)، و Low-Level Module هي اللي هتقوم بتنفيذ التفاصيل.

وده معناه إننا ممكن نغير طريقة إرسال الإشعار (من البريد الإلكتروني إلى الرسائل النصية مثلاً) من غير ما نعدل High-Level Module.

مش فاهم بردو !!

https://miro.medium.com/v2/resize:fit:625/1*Qk8tDmjQlyvwKxNTfXIo0Q.png
Dependency Inversion

الجزء اليسار (الروبوت الوردي): الروبوت هنا معتمد بشكل كبير على أداة محددة (ذراع قطع البيتزا)، يعني لو حصل أي تغيير في الأداة، سيحتاج الروبوت إلى تعديل. ده معناه أن الروبوت مرتبط بشكل مباشر بالتفاصيل (الذراع) ويخالف مبدأ Dependency Inversion.

الروبوت هنا بيقول: "I cut pizza with my pizza cutter arm"
يعني بيستخدم فقط الأداة اللي مصمم بيها ذراعه، لو الأداة دي باظت أو محتاج أداة تانية، الروبوت مش هيقدر يشتغل.

الجزء اليمين (الروبوت الأخضر): هنا الروبوت مصمم بطريقة مرنة، بيعتمد على التجريدات. الروبوت مش مرتبط بأداة معينة لقطع البيتزا، وبيقول: "I cut pizza with any tool given to me". يعني الروبوت يقدر يستخدم أي أداة سواء كان ذراع قطع بيتزا أو أي أداة تانية متاحة. هنا التصميم بيعتمد على التجريدات بدل من التفاصيل، وده بيحقق مبدأ Dependency Inversion.

العلاقة بين الصورة والمبدأ:

  1. في الجزء الوردي: الروبوت بيعتمد على تفاصيل منخفضة المستوى (الذراع المخصص لقطع البيتزا)، وده بيخالف المبدأ لأن أي تغيير في الأداة هيؤدي لتغيير في الكود الخاص بالروبوت.
  2. في الجزء الأخضر: الروبوت بيعتمد على abstractions (يقبل أي أداة لقطع البيتزا)، مما يخليه مرن ومتماشٍ مع مبدأ Dependency Inversion، وبالتالي لو حصل أي تغيير في الأدوات، مش هيحتاج الروبوت نفسه لتعديل.

Simple Email & Notification Service Example

// وحدة منخفضة المستوى للإرسال البريد الإلكتروني
class EmailSender {
  void sendEmail(String message) {
    print('Sending email: $message');
  }
}
// وحدة عالية المستوى تعتمد على وحدة البريد الإلكتروني مباشرة
class NotificationService {
  final EmailSender emailSender;

  NotificationService(this.emailSender);

  void notifyUser(String message) {
    emailSender.sendEmail(message);
  }
}
void main() {
  EmailSender emailSender = EmailSender();
  NotificationService notificationService = NotificationService(emailSender);
  notificationService.notifyUser('Welcome to our service!');
}

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

الحل:

abstract class INotificationSender {
  void send(String message);
}
class EmailSender implements INotificationSender {
  @override
  void send(String message) {
    print('Sending email: $message');
  }
}
class SmsSender implements INotificationSender {
  @override
  void send(String message) {
    print('Sending SMS: $message');
  }
}
class NotificationService {
  final INotificationSender notificationSender;

  NotificationService(this.notificationSender);

  void notifyUser(String message) {
    notificationSender.send(message);
  }
}
void main() {
  // إرسال إشعار باستخدام البريد الإلكتروني
  INotificationSender emailSender = EmailSender();
  NotificationService notificationService = NotificationService(emailSender);
  notificationService.notifyUser('Welcome to our service!');

  // إرسال إشعار باستخدام الرسائل النصية
  INotificationSender smsSender = SmsSender();
  NotificationService notificationService = NotificationService(smsSender);
  notificationService.notifyUser('Your package has been delivered!');
}

  • الوحدة عالية المستوى NotificationService تعتمد الآن على الواجهة INotificationSender بدلاً من الاعتماد على تفاصيل الوحدات مثل EmailSender و SmsSender.
  • يمكنك إضافة أنواع جديدة من الوحدات منخفضة المستوى بسهولة (مثل PushNotificationSender للإشعارات الفورية) دون تغيير في الكود عالي المستوى.
  • أصبح الكود أكثر مرونة وقابلية للصيانة والتوسيع.

في الختام

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