مبادئ SOLID في TypeScript: النظرية والتطبيق من الألف إلى الياء
\n\nSOLID هو اختصار لخمسة مبادئ أساسية في البرمجة كائنية التوجه، صاغها روبرت مارتن (العم بوب). تساعد هذه المبادئ في إنشاء كود مرن وقابل للصيانة والتوسع. في هذه المقالة، سنشرح بالتفصيل كل مبدأ من مبادئ SOLID مع أمثلة على TypeScript، من التعريفات البسيطة إلى سيناريوهات الاستخدام الواقعية.
\n\n1. مبدأ المسؤولية الواحدة (Single Responsibility Principle - SRP)
\n\nالتعريف: يجب أن يكون لكل class سبب واحد فقط للتغيير. بمعنى آخر، يجب أن يكون class مسؤولاً عن مهمة واحدة محددة بوضوح.
\n\nالمشكلة: عندما يتحمل class عدة مسؤوليات، يصبح من الصعب اختباره وتوسيعه وصيانته. قد يؤدي تغيير وظيفة واحدة إلى تعطيل أخرى.
\n\nمثال على انتهاك SRP:
\n// ❌ انتهاك SRP: class يقوم بالتخزين والتنسيق والإرسال\nclass UserService {\n saveUser(user: User): void {\n // حفظ في قاعدة البيانات\n console.log('User saved');\n }\n\n formatUserData(user: User): string {\n return `Name: ${user.name}, Email: ${user.email}`;\n }\n\n sendWelcomeEmail(user: User): void {\n // إرسال بريد إلكتروني\n console.log('Email sent');\n }\n}\n\nالحل مع SRP:
\n// ✅ كل class مسؤول عن مهمته الخاصة\nclass UserRepository {\n save(user: User): void {\n console.log('User saved');\n }\n}\n\nclass UserFormatter {\n format(user: User): string {\n return `Name: ${user.name}, Email: ${user.email}`;\n }\n}\n\nclass EmailService {\n sendWelcome(user: User): void {\n console.log('Email sent');\n }\n}\n\n2. مبدأ الفتح/الإغلاق (Open-Closed Principle - OCP)
\n\nالتعريف: يجب أن تكون class مفتوحة للتمديد ولكن مغلقة للتعديل. هذا يعني أنه يجب أن تكون قادراً على إضافة وظائف جديدة دون تغيير الكود الموجود.
\n\nمثال على انتهاك OCP:
\n// ❌ عند إضافة نوع جديد من الأشكال، يجب تعديل طريقة calculateArea\nclass AreaCalculator {\n calculateArea(shape: any): number {\n if (shape.type === 'circle') {\n return Math.PI * shape.radius ** 2;\n } else if (shape.type === 'rectangle') {\n return shape.width * shape.height;\n }\n throw new Error('Unknown shape');\n }\n}\n\nالحل مع OCP (باستخدام تعدد الأشكال):
\n// ✅ استخدام واجهة interface والوراثة\ninterface Shape {\n area(): number;\n}\n\nclass Circle implements Shape {\n constructor(private radius: number) {}\n area(): number {\n return Math.PI * this.radius ** 2;\n }\n}\n\nclass Rectangle implements Shape {\n constructor(private width: number, private height: number) {}\n area(): number {\n return this.width * this.height;\n }\n}\n\nclass AreaCalculator {\n calculateArea(shape: Shape): number {\n return shape.area(); // لا يتطلب تغييرات عند إضافة أشكال جديدة\n }\n}\n\n3. مبدأ استبدال ليسكوف (Liskov Substitution Principle - LSP)
\n\nالتعريف: يجب أن تكون الكائنات في البرنامج قابلة للاستبدال بنسخ من أنواعها الفرعية دون التأثير على صحة تنفيذ البرنامج. يجب ألا ينتهك class الفرعي عقد class الأصلي.
\n\nمثال على انتهاك LSP:
\n// ❌ انتهاك: class الفرعي يغير سلوك class الأصلي\nclass Bird {\n fly(): void {\n console.log('Flying');\n }\n}\n\nclass Penguin extends Bird {\n fly(): void {\n throw new Error('Penguins cannot fly!'); // انتهاك العقد\n }\n}\n\nالحل مع LSP:
\n// ✅ فصل