مقدمة: SOLID في نظام JavaScript البيئي
\nSOLID هو اختصار لخمسة مبادئ أساسية في التصميم كائني التوجه، صاغها روبرت مارتن. في سياق JavaScript، خاصة عند التطوير على Node.js باستخدام TypeScript والأطر الحديثة (NestJS, Express)، تصبح هذه المبادئ بالغة الأهمية لبناء تطبيقات قابلة للتوسع والصيانة. على المستوى المتقدم، يتجاوز SOLID حدود الفئات والواجهات البسيطة، ليشمل بنية الوحدات، وحقن التبعيات، والاختبار.
\nفي هذه المقالة، سنستعرض كل مبدأ من مبادئ SOLID مع أمثلة برمجية لـ Node.js، مع التركيز على الجوانب العملية والأخطاء الشائعة. ستتعلم كيفية تطبيق SOLID في المشاريع الحقيقية باستخدام إمكانيات JavaScript (ES6+) و TypeScript.
\n\n1. مبدأ المسؤولية الواحدة (SRP) في JavaScript
\nالنظرية: يجب أن يكون للفئة أو الوحدة سبب واحد فقط للتغيير. في JavaScript، غالبًا ما يُنتهك هذا المبدأ عندما يتم خلط منطق الأعمال، والتعامل مع قاعدة البيانات، وتنسيق الردود في ملف واحد.
\nالتطبيق (Node.js): نفصل المسؤوليات على مستوى الخدمات والمستودعات.
\n// ❌ انتهاك SRP: الفئة تعالج الطلبات، وتتحقق من الصحة، وتحفظ\nclass UserService {\n createUser(data) {\n if (!data.email) throw new Error('Email required');\n const db = new Database();\n return db.save('users', data);\n }\n}\n\n// ✅ الامتثال لـ SRP: فصل إلى مدقق، ومستودع، وخدمة\nclass UserValidator {\n validate(data) {\n if (!data.email) throw new Error('Email required');\n }\n}\n\nclass UserRepository {\n constructor(db) {\n this.db = db;\n }\n save(data) {\n return this.db.save('users', data);\n }\n}\n\nclass UserService {\n constructor(validator, repository) {\n this.validator = validator;\n this.repository = repository;\n }\n createUser(data) {\n this.validator.validate(data);\n return this.repository.save(data);\n }\n}\nاستخدم حقن التبعيات (DI) لتمرير المدقق والمستودع إلى الخدمة. هذا يبسط الاختبار واستبدال المكونات.
\n\n2. مبدأ الانفتاح/الإغلاق (OCP) من خلال تعدد الأشكال
\nالنظرية: يجب أن تكون الفئات مفتوحة للتمديد ولكن مغلقة للتعديل. في JavaScript، يُحقق ذلك من خلال الوراثة، أو التركيب، أو الاستراتيجيات.
\nالتطبيق (Node.js مع TypeScript): نستخدم الفئات المجردة أو الواجهات لتحديد العقد.
\n// فئة مجردة للتقارير\nabstract class ReportGenerator {\n abstract generate(data: any): string;\n}\n\nclass PDFReport extends ReportGenerator {\n generate(data: any): string {\n return `PDF: ${JSON.stringify(data)}`;\n }\n}\n\nclass CSVReport extends ReportGenerator {\n generate(data: any): string {\n return `CSV: ${data.join(',')}`;\n }\n}\n\n// الكود العميل لا يتغير عند إضافة تنسيقات جديدة\nclass ReportService {\n constructor(private generator: ReportGenerator) {}\n createReport(data: any) {\n return this.generator.generate(data);\n }\n}\nلـ JavaScript الخالص، استخدم المصانع أو الاستراتيجيات (نمط Strategy) لتجنب تعديل الكود الموجود.
\n\n3. مبدأ استبدال ليسكوف (LSP) في الكود غير المتزامن
\nالنظرية: يجب أن تكون كائنات الفئة الأساسية قابلة للاستبدال بكائنات الفئة المشتقة دون الإخلال بعمل البرنامج. في JavaScript، غالبًا ما يُنتهك LSP عند إعادة تعريف دوال بعقود مختلفة (مثل أنواع الإرجاع أو الاستثناءات).
\nالتطبيق (Node.js مع async/await): تأكد من أن الفئات الفرعية لا تضعف الشروط المسبقة ولا تقوي الشروط اللاحقة.
\nclass Database {\n async connect() { return 'con