مقدمة: ما هو حقن التبعية ولماذا هو مطلوب؟
\nتخيل أنك تبني منزلاً. أنت بحاجة إلى الطوب والأسمنت والأدوات. بدلاً من الذهاب إلى المستودع في كل مرة، يمكنك ببساطة طلب توصيل جميع المواد إلى موقع البناء. حقن التبعية (Dependency Injection) أو DI هو نفس المبدأ، ولكن لكودك.
\nحقن التبعية هو نمط تصميم حيث يتلقى الكائن تبعياته من الخارج، بدلاً من إنشائها بنفسه. وهذا يجعل الكود أكثر مرونة وقابلية للاختبار وسهولة في الصيانة. في هذه المقالة، سنشرح DI باستخدام Kotlin - لغة برمجة حديثة مثالية لتعلم هذا النمط.
\n\nالمشكلة: لماذا يصبح الكود هشاً بدون DI؟
\nلنأخذ مثالاً نموذجياً. لنفترض أن لدينا كلاس UserService يرسل إشعارات البريد الإلكتروني. بدون DI، سيبدو هكذا:
class EmailService {\n fun sendEmail(to: String, message: String) {\n println("إرسال بريد إلكتروني إلى $to: $message")\n }\n}\n\nclass UserService {\n private val emailService = EmailService() // ارتباط صارم!\n\n fun notifyUser(email: String) {\n emailService.sendEmail(email, "مرحباً أيها المستخدم!")\n }\n}\nما هي المشكلة؟ UserService مرتبط بشكل صارم بتنفيذ محدد لـ EmailService. إذا أردت:
\n- \n
- استبدال EmailService بـ SmsService؛ \n
- إضافة تسجيل (logging)؛ \n
- كتابة اختبارات وحدة باستخدام كائن وهمي (mock)، \n
— سيتعين عليك تغيير كود UserService. وهذا ينتهك مبدأ Open/Closed (الانفتاح/الإغلاق) ويجعل الاختبار أكثر صعوبة.
\n\nالحل: حقن التبعية يدوياً في Kotlin
\nأبسط طريقة لتنفيذ DI هي تمرير التبعية عبر المُنشئ. في Kotlin، يتم ذلك بشكل موجز جداً:
\n// 1. إنشاء واجهة (تجريد)\ninterface NotificationService {\n fun send(to: String, message: String)\n}\n\n// 2. تنفيذ خدمات محددة\nclass EmailService : NotificationService {\n override fun send(to: String, message: String) {\n println("بريد إلكتروني إلى $to: $message")\n }\n}\n\nclass SmsService : NotificationService {\n override fun send(to: String, message: String) {\n println("رسالة نصية إلى $to: $message")\n }\n}\n\n// 3. حقن التبعية عبر المُنشئ\nclass UserService(private val notificationService: NotificationService) {\n fun notifyUser(email: String) {\n notificationService.send(email, "مرحباً!")\n }\n}\n\n// 4. الاستخدام\nfun main() {\n val emailService = EmailService()\n val userService = UserService(emailService) // حقن!\n userService.notifyUser("user@example.com")\n}\nمزايا هذا النهج:
\n- \n
- المرونة: يمكن بسهولة استبدال
EmailServiceبـSmsService؛ \n - قابلية الاختبار: في الاختبارات، يمكنك تمرير كائن وهمي: \n
class MockNotificationService : NotificationService {\n var lastMessage: String? = null\n override fun send(to: String, message: String) {\n lastMessage = message\n }\n}\n\nfun test() {\n val mock = MockNotificationService()\n val userService = UserService(mock)\n userService.notifyUser("test@test.com")\n assert(mock.lastMessage == "مرحباً!")\n}\n\nأطر عمل DI: Koin و Dagger لـ Kotlin
\nمع DI اليدوي، إذا كان المشروع كبيراً، ستكتب الكثير من الكود المتكرر. هنا تأتي أطر عمل DI للمساعدة. في عالم Kotlin، هناك اثنان مشهوران: Koin (بسيط) و Dagger/Hilt (قوي).
\n\nKoin: البساطة والسرعة
\nKoin هو إطار عمل DI خفيف الوزن، مكتوب بلغة Kotlin. لا يتطلب توليد كود وي