في هذه الصفحة
المقدمة
الـ SOLID Principles عبارة عن مجموعة من القواعد البسيطة بتساعد المبرمجين على كتابة كود نظيف ومنظم وسهل الفهم والتعديل. تخيل كأنك بتبني بيت، لازم يكون كل جزء فيه له وظيفة واضحة ومكان محدد عشان البيت يبقى قوي ومستقر.
نفس الكلام في البرمجة، الكود لازم يكون منظّم عشان يسهل تطويره وصيانته!
ليه المبادئ دي مهمة؟
- كود نظيف ومنظم: بيسهل فهمه وتعديله
- صيانة أسهل: لو فيه أي مشكلة، هتلاقيها بسرعة وتصلحها
- توسعة أسرع: تقدر تضيف ميزات جديدة بسهولة
- تعاون أفضل بين المبرمجين: كل واحد هيفهم شغله كويس
- كفاءة أعلى: الكود هيشتغل بشكل أسرع وأكثر استقرارًا
مبدأ الـ Open Close
أنا مش فاهم حاجة!
الحاجات اللي بنبني بيها البرامج (زي الوظائف والأجزاء الكبيرة والصغيرة) لازم تكون مفتوحة عشان نقدر نزود عليها حاجات جديدة، بس في نفس الوقت تكون مقفولة عشان محدش يغير فيها حاجة قديمة.
يعني إيه الكلام ده ببساطة؟ تخيل إنك بتبني بيت ، البيت ده زي برنامج، والأجزاء بتاعته زي الغرف والحمامات والمطبخ. دلوقتي، لو عايز تزود غرفة جديدة، هتضيفها للبيت من غير ما تخرب الغرف اللي موجودة. بس مش هتروح تغير في شكل الغرف القديمة وتبوظ الديكور بتاعها، صح؟
ليه الكلام ده مهم في البرمجة؟
- سهولة التعديل: لو عايز تزود حاجة جديدة على البرنامج، هتعرف تعملها بسهولة من غير ما تخرب الحاجات اللي شغالة.
- أقل أخطاء: لما بتغير في حاجة شغالة، ممكن تخليها تبوظ. فلو زودت حاجة جديدة من غير ما تغير في القديم، هتقلل الأخطاء.
- شغل جماعي: لو كتير من المبرمجين بيشتغلوا على نفس البرنامج، كل واحد هيقدر يضيف حاجة جديدة من غير ما يتعارض مع شغل التاني. مثال تاني: تخيل إنك عندك لعبة فيديو. اللعبة دي فيها شخصيات مختلفة، كل شخصية ليها مهارات معينة. دلوقتي، عايزين نزود شخصية جديدة للعبة. بدل ما نغير في الشخصيات القديمة، هنعمل شخصية جديدة خالص مع مهارات جديدة. كده هنكون زودنا اللعبة من غير ما نخرب الشخصيات القديمة.
باختصار: الفكرة كلها إننا نبني البرامج بطريقة ذكية، بحيث نقدر نضيف أي حاجة جديدة من غير ما نخرب الحاجات القديمة. كده البرنامج بتاعنا هيبقى أكثر مرونة وأسهل في التطوير.
نخش في الاكواد 💪
Simple Salary Calculator Example
class Employee {
String name;
double salary;
void calculateSalary() {
// هنا يتم حساب الراتب بطريقة ثابتة //
// مثلا
salary = 1000; //
}
}
ايه المشكلة في الكود ده ؟ لو عايز اضيف طريقة تانية لحساب الراتب مثال عندي موظف شغال مش براتب ثابت أو بيقبض باليوم كده هتعدل في الكلاس نفسه و ده غلط طيب و الحل !
abstract class SalaryCalculator {
double calculateSalary(Employee employee);
}
class BaseSalaryCalculator implements SalaryCalculator {
@override
double calculateSalary(Employee employee) {
return 1000; // راتب أساسي
}
}
class ExperienceBasedSalaryCalculator implements SalaryCalculator {
@override
double calculateSalary(Employee employee) {
// حساب الراتب بناء على الخبرة
return 1000 + employee.experience * 50;
}
}
class Employee {
String name;
SalaryCalculator calculator;
Employee(this.name, this.calculator);
}
Shipping Orders & Payment Methods Example
في الكود هنا عندي اكتر من ميثود واحدة علشان payment و التانية علشان الشحن طيب لو عندي اكتر من وسيلة دفع و اكتر من طريقة شحن هنعمل ايه كده هتعدل في الكلاس و ده ضد مبدأ OCP طيب و الحل!
class Product {
String name;
double price;
}
class Order {
List<Product> products = [];
double total = 0;
void addProduct(Product product) {
products.add(product);
calculateTotal();
}
void calculateTotal() {
total = products.fold(0, (sum, product) => sum + product.price);
}
void processPayment() {
// منطق معالجة الدفع البسيط (مثال: نقداً)
print("Payment processed");
}
void shipOrder() {
// منطق الشحن البسيط (مثال: شحن عادي)
print("Order shipped");
}
}
تقدروا دلوقتي تشتركوا في النشرة الأسبوعية لاقرأ-تِك بشكل مجاني تمامًا عشان يجيلكوا كل جديد بشكل أسبوعي فيما يخص مواضيع متنوعة وبشروحات بسيطة وسهلة وبجودة عالية 🚀
النشرة هيكون ليها شكل جديد ومختلف عن شكلها القديم وهنحاول انها تكون مميزة ومختلفة وخليط بين المحتوى الأساسي اللي بينزل ومفاجآت تانية كتير 🎉
بفضل الله قمنا بإطلاق قناة اقرأ-تِك على التليجرام مجانًا للجميع 🚀
آملين بده اننا نفتح باب تاني لتحقيق رؤيتنا نحو إثراء المحتوى التقني باللغة العربية ، ومساعدة لكل متابعينا في انهم يوصلوا لجميع أخبار اقرأ-تِك من حيث المقالات ومحتوى ورقة وقلم والنشرة الأسبوعية وكل جديد بطريقة سريعة وسهلة
مستنينكوا تنورونا , وده رابط القناة 👇
المشاكل اللي بتحصل لما بنكتب الكود من غير ما نفكر في OCP:
- نضطر نعدل الكود القديم كل شوية: تخيل إنك عايز تضيف طريقة دفع جديدة زي الفيزا، هتضطر تدخل على كل حتة في الكود بتعمل حساب الدفع وتغيرها، ده بيزود فرصة الغلط.
- الكود بيكبر ويبوظ: لو عندك منتجات كتير أنواعها مختلفة، الكود اللي بيمثل المنتج هيطول أوي وهيخرب شكل الكود كله.
- صعب نختبر الكود: لو الكود مرتبط بحاجات تانية زي الواجهة بتاعة البرنامج أو قاعدة البيانات، هيبقى صعب نعرفه شغال صح ولا لأ.
الحلول اللي بنستخدمها عشان نحل المشاكل دي:
- نستخدم الوراثة والتعدد الشكلي: يعني نقسم حاجات زي طرق الدفع لـ أنواع مختلفة (نقدي، فيزا، ...) وكل نوع ليه وظيفته الخاصة، وبكده لو عايزين نضيف طريقة دفع جديدة بنعمل نوع جديد بس من غير ما نغير في الأنواع القديمة.
- نستخدم الواجهات Interfaces: زي ما بنعمل عقد بيننا وبين حد، بنعمل عقد للمنتج يقول إن أي منتج لازم يعرف سعره ووصفه، وبكده بنضمن إن كل المنتجات بتتبع نفس الشكل.
- نستخدم Factory Pattern: تخيل إنك عندك مصنع بيصنع أنواع مختلفة من السيارات، كل ما تحتاج نوع جديد بتقول للمصنع يصنعهولك، نفس الفكرة هنا، بنعمل مصنع يصنع لنا أنواع مختلفة من المنتجات أو طرق الدفع.
- نستخدم Strategy Pattern: يعني نقسم الحاجات اللي بتتغير كتير (زي طريقة حساب الضريبة) عن باقي الكود، وبكده لو غيرنا طريقة الحساب هنغير في مكان واحد بس.
- نستخدم Observer Pattern: تخيل إنك عندك مجموعة ناس عايزة تعرف لما يحصل حاجة معينة، زي لما يتضاف منتج جديد، بنستخدم المراقبة عشان نخبرهم بالتغيير ده.
الحل الصحيح :
abstract class PaymentMethod {
void processPayment(Order order);
}
abstract class ShippingMethod {
void shipOrder(Order order);
}
abstract class Observer {
void update(Order order);
}
class Order {
List<Product> products = [];
PaymentMethod paymentMethod;
ShippingMethod shippingMethod;
List<Observer> observers = [];
void addProduct(Product product) {
products.add(product);
notifyObservers();
}
void setPaymentMethod(PaymentMethod paymentMethod) {
this.paymentMethod = paymentMethod;
}
void setShippingMethod(ShippingMethod shippingMethod) {
this.shippingMethod = shippingMethod;
}
void placeOrder() {
paymentMethod.processPayment(this);
shippingMethod.shipOrder(this);
notifyObservers();
}
void notifyObservers() {
for (var observer in observers) {
observer.update(this);
}
}
}
مميزات الحل ده:
- نقدر نضيف حاجات جديدة للكود بسهولة من غير ما نبوظ اللي موجود.
- الكود بيبقى أكثر مرونة وقادر على التغيير.
- بيبقى سهل نختبر كل جزء من الكود لوحده.
ملاحظات:
- اختار الحل اللي يناسب مشروعك وحجمه.
- ممكن تستخدم أكتر من طريقة مع بعض.
- حاول تكتب كود واضح ومفهوم عشان يسهل عليك وعلى غيرك فهمه وتعديله.
نجيب مثال قديم من مشروع كنت عامله و نعمل له تعديل علشان يطبق مبدأ OCP
Quiz App Example
في المثال هنا عندي كلاس مسؤول عن طريقة الاسئلة في التطبيق يعني الاسئلة صح و غلط ولا اختيار من متعدد ؟ طيب لو حبيت اضيف طريقة تانية مثلا الاسئلة المقالي ، طيب لو عايز اضيف طريقة رابعة إن الطالب يرفع صوت ب الاجابة ؟
class FirestoreService {
void addMultipleChoiceQuestion(String question, List<String> choices, String answer) {
FirebaseFirestore.instance.collection('Questions').add({
'question': question,
'choices': choices,
'answer': answer,
'type': 'multiple_choice'
});
}
void addTrueFalseQuestion(String question, bool answer) {
FirebaseFirestore.instance.collection('Questions').add({
'question': question,
'answer': answer,
'type': 'true_false'
});
}
// إضافة جديدة للأسئلة الشرحية
void addExplanationQuestion(String question, String explanation) {
FirebaseFirestore.instance.collection('Questions').add({
'question': question,
'explanation': explanation,
'type': 'explanation'
});
}
}
الحل طبقا لمبدأ OCP :
abstract class Question {
void addQuestion();
}
class MultipleChoiceQuestion implements Question {
final String question;
final List<String> choices;
final String answer;
MultipleChoiceQuestion(this.question, this.choices, this.answer);
@override
void addQuestion() {
FirebaseFirestore.instance.collection('Questions').add({
'question': question,
'choices': choices,
'answer': answer,
'type': 'multiple_choice'
});
}
}
class TrueFalseQuestion implements Question {
final String question;
final bool answer;
TrueFalseQuestion(this.question, this.answer);
@override
void addQuestion() {
FirebaseFirestore.instance.collection('Questions').add({
'question': question,
'answer': answer,
'type': 'true_false'
});
}
}
class ExplanationQuestion implements Question {
final String question;
final String explanation;
ExplanationQuestion(this.question, this.explanation);
@override
void addQuestion() {
FirebaseFirestore.instance.collection('Questions').add({
'question': question,
'explanation': explanation,
'type': 'explanation'
});
}
}
التحسين:
- التزام بمبدأ OCP: تم إنشاء abstraction Question لتمثيل أي نوع من الأسئلة بشكل عام. ثم، تم إنشاء classes فرعية MultipleChoiceQuestion, TrueFalseQuestion, و ExplanationQuestion لتمثل أنواع الأسئلة المختلفة.
- الوراثة: تم استخدام الوراثة لإنشاء أنواع الأسئلة المختلفة من الصف الأساسي Question. التعدد الشكلي: يمكن الآن التعامل مع جميع أنواع الأسئلة كنوع من نوع Question.
- التوسعة: يمكن بسهولة إضافة أنواع جديدة من الأسئلة عن طريق إنشاء صفوف فرعية جديدة من Question.
الفوائد:
- مرونة: يمكن إضافة أنواع جديدة من الأسئلة دون تعديل الكود الأصلي.
- صيانة أسهل: يصبح الكود أكثر تنظيماً وسهل الصيانة.
- إعادة الاستخدام: يمكن استخدام نفس الكود للتعامل مع أنواع مختلفة من الأسئلة.
في الختام
بكده نكون شوفنا مع بعض أهمية مبدأ الـ Open Close في التوسع واضافة خصائص جديدة بدون التاثير والتغيير على الخصائص الحالية وكمان شوفنا قد ايه بيتميز بالمرونة والصيانة الأسهل وبرضو إعادة الاستخدام.
ده كان تاني مبدأ من الـ SOLID Principles ولسه فيه مبادئ تانية هنتكلم عنها باذن الله.