مبادئ SOLID في Kotlin: من النظرية إلى التطبيق
\n\nSOLID هو اختصار لخمسة مبادئ أساسية للبرمجة كائنية التوجه، صاغها روبرت مارتن. تساعد هذه المبادئ في إنشاء كود مرن وقابل للصيانة والتوسع. في هذه المقالة، سنشرح بالتفصيل كل مبدأ من مبادئ SOLID مع أمثلة من التطوير الفعلي في Kotlin.
\n\nفهم SOLID أمر بالغ الأهمية لأي مطور Kotlin، خاصة عند العمل مع أطر عمل مثل Spring Boot أو Ktor. تشكل هذه المبادئ أساس العمارة النظيفة وأنماط التصميم.
\n\n1. مبدأ المسؤولية الواحدة (SRP)
\n\nالجوهر: يجب أن يكون لكل فئة سبب واحد فقط للتغيير. بمعنى آخر، يجب أن تكون الفئة مسؤولة عن وظيفة واحدة.
\n\nفي Kotlin، هذا المبدأ مهم بشكل خاص بسبب دعم الأسلوب الوظيفي. لنلق نظرة على مثال سيء:
\n\n// ❌ انتهاك SRP: الفئة تتعامل مع التسجيل والعمل مع قاعدة البيانات\nclass UserService {\n fun saveUser(user: User) {\n // التسجيل\n println("Saving user: ${user.name}")\n // منطق الأعمال\n validateUser(user)\n // العمل مع قاعدة البيانات\n database.insert(user)\n }\n}\n\n\nلنصلح الموقف بتقسيم المسؤولية:
\n\n// ✅ النهج الصحيح: كل مسؤولية في فئة منفصلة\nclass Logger {\n fun log(message: String) = println("[LOG] $message")\n}\n\nclass UserValidator {\n fun validate(user: User): Boolean = user.name.isNotBlank()\n}\n\nclass UserRepository {\n fun save(user: User) { /* حفظ في قاعدة البيانات */ }\n}\n\nclass UserService(\n private val logger: Logger,\n private val validator: UserValidator,\n private val repository: UserRepository\n) {\n fun saveUser(user: User) {\n logger.log("Saving user: ${user.name}")\n if (validator.validate(user)) {\n repository.save(user)\n }\n }\n}\n\n\n2. مبدأ الفتح/الإغلاق (OCP)
\n\nالجوهر: يجب أن تكون الفئات مفتوحة للتمديد ولكن مغلقة للتعديل. يجب إضافة الوظائف الجديدة من خلال الوراثة أو التركيب، دون تغيير الكود الموجود.
\n\nفي Kotlin، تعتبر الفئات المختومة (sealed classes) والواجهات مثالية لتنفيذ OCP:
\n\n// ❌ انتهاك OCP: عند إضافة نوع جديد من الأشكال، يجب تغيير الفئة\nclass AreaCalculator {\n fun calculate(shape: Any): Double = when (shape) {\n is Circle -> 3.14 * shape.radius * shape.radius\n is Rectangle -> shape.width * shape.height\n else -> throw IllegalArgumentException()\n }\n}\n\n\nالتنفيذ الصحيح باستخدام تعدد الأشكال:
\n\n// ✅ النهج الصحيح: إضافة أشكال جديدة دون تغيير الكود الموجود\ninterface Shape {\n fun area(): Double\n}\n\nclass Circle(val radius: Double) : Shape {\n override fun area() = Math.PI * radius * radius\n}\n\nclass Rectangle(val width: Double, val height: Double) : Shape {\n override fun area() = width * height\n}\n\n// الآن يمكن إضافة مثلث دون تغيير AreaCalculator\nclass Triangle(val base: Double, val height: Double) : Shape {\n override fun area() = 0.5 * base * height\n}\n\nclass AreaCalculator {\n fun calculate(shape: Shape) = shape.area()\n}\n\n\n3. مبدأ استبدال ليسكوف (LSP)
\n\nالجوهر: يجب أن تكون كائنات الفئة الأساسية قابلة للاستبدال بكائنات الفئات المشتقة دون الإخلال بعمل البرنامج. يجب ألا تضعف الفئة الفرعية الشروط المسبقة أو تقوي الشروط اللاحقة.
\n\nلنلق نظرة على المثال الكلاسيكي للمستطيل والمربع:
\n\n// ❌ انتهاك LSP: المربع لا يمكن أن يحل محل المستطيل\nopen class Rectangle(\n open var width: Int,\n open var height: Int\n) {\n fun area() = width * height\n}\n\nclass Square(sid