شرح حقن التبعيات
شرح حقن التبعيات
حقن التبعيات (Dependency Injection — DI) من تلك الأفكار التي تبدو مجردة في المرة الأولى التي تصادفها، غير أن كل قاعدة كود Java احترافية تعتمد عليها. في جوهرها، الفكرة بسيطة: لا تنشئ الفئة الكائنات التي تعتمد عليها — بل يُوفّرها شيء خارجها. هذا التحوّل في المسؤولية هو كل الفكرة، وتداعياته على التصميم وقابلية الاختبار والصيانة عميقة جدًا.
المشكلة التي يحلّها حقن التبعيات
لنبدأ بنقطة انطلاق واقعية: خدمة OrderService ترسل رسائل تأكيد بالبريد الإلكتروني.
يبدو هذا بسيطًا، لكنه يُنشئ عدة مشاكل خفية:
- تنفيذ مُضمَّن في الكود.
OrderServiceمرتبطة بشكل دائم بـSmtpEmailClient. التحوّل إلى SendGrid أو SES يستلزم تعديل هذه الفئة لا مجرد تغيير الإعدادات. - يستحيل اختبار الوحدة بشكل معزول. كل اختبار يستدعي
placeOrderسيحاول فتح مقبس SMTP حقيقي. تصبح الاختبارات بطيئة وغير موثوقة وتعتمد على البنية التحتية. - إعدادات مخفية. مضيف SMTP ومنفذه مدفونان داخل
OrderService. تغييرهما بين بيئة التطوير والإنتاج يتطلب تعديل كود منطق الأعمال. - اقتران وثيق (Tight Coupling). إن تغيّر مُنشئ
SmtpEmailClient، وجب تغييرOrderServiceأيضًا، حتى لو لم يتغيّر العقد الأعلى مستوى (إرسال البريد الإلكتروني).
حقن التبعيات كحل
الحل مباشر: بدلًا من إنشاء SmtpEmailClient داخليًا، اقبل واجهة EmailClient من الخارج.
الآن يمكن للاختبار حقن بديل خفيف الوزن دون أي اتصال بـ SMTP:
أنواع الحقن الثلاثة
ثمة ثلاث آليات قياسية لتسليم التبعية إلى فئة ما. لكلٍّ منها تركيب نحوي مختلف ومقايضات مختلفة — دروس لاحقة تتناول كلًا منها بالتفصيل، لكنك تحتاج الآن للتعرف عليها.
1. حقن المُنشئ (Constructor Injection)
تُمرَّر التبعية كمعامل للمُنشئ. هذا هو الأسلوب المفضّل في تطبيقات Spring الحديثة.
المزايا: يمكن جعل الحقل final (يضمن عدم كونه null بعد الإنشاء)؛ يستحيل إنشاء الفئة دون تبعياتها؛ وشجرة التبعيات صريحة في توقيع المُنشئ.
2. حقن المُعيِّن (Setter Injection)
تُوفَّر التبعية عبر دالة معيِّن بعد إنشاء الكائن.
مفيد حين تكون التبعية اختيارية فعلًا، أو حين تحتاج لدعم المراجع الدائرية (وإن كانت المراجع الدائرية تشير عادةً إلى مشكلة في التصميم). الجانب السلبي: لا يمكن جعل الحقل final، لذا يمكن نظريًا استبدال التبعية أو تركها null.
3. حقن الحقل (Field Injection)
يحقن إطار العمل مباشرةً في الحقل باستخدام الانعكاس (reflection)، متجاوزًا المُنشئات والمُعيِّنات كليًا.
final، التبعية غير مرئية في الواجهة البرمجية العامة، وإنشاء الفئة في اختبار وحدة دون سياق Spring يستلزم حيلًا بالانعكاس. فضّل حقن المُنشئ لجميع التبعيات الإلزامية. احتفظ بحقن الحقل للكود النمطي قصير العمر أو فئات الاختبار فقط (@MockBean في اختبارات Spring Boot حالة استخدام مشروعة).
لماذا يُحسِّن حقن التبعيات التصميم
يُطبِّق DI مبدأ انعكاس التبعيات (حرف D في SOLID): يجب أن تعتمد الوحدات عالية المستوى (OrderService) على التجريدات (EmailClient)، لا على التنفيذات الفعلية (SmtpEmailClient). لهذا تداعيات عملية مباشرة:
- تبديل التنفيذات دون تغيير منطق الأعمال. وجِّه رسائل البريد عبر طابور لتحسين الأداء، أو أسكتها في بيئة التجهيز، بتوفير bean مختلف لـ
EmailClient—OrderServiceلا تُمَسّ. - اختبار كل فئة باستقلالية. يوفّر اختبار الوحدة تبعية وهمية أو محاكاة؛ لا حاوية ولا قاعدة بيانات ولا SMTP. تعمل الاختبارات في ميلي ثانية.
- الإعدادات في جذر التركيب. المكان الذي تُربط فيه الكائنات معًا (فئة
@Configurationفي Spring أو سياق التطبيق) هو المكان الوحيد الذي يعرف الأنواع الفعلية والإعدادات الخارجية. تظل فئات منطق الأعمال جاهلة بتفاصيل البنية التحتية. - التطوير المتوازي. يمكن للفرق العمل على
OrderServiceوSmtpEmailClientفي آن واحد، متفقين فقط على عقد واجهةEmailClient.
حاوية Spring بوصفها المُحقِن
في تطبيق Java عادي، تربط التبعيات يدويًا في دالة main — يُسمّى هذا جذر التركيب (Composition Root):
في تطبيق Spring، يُعدّ ApplicationContext جذرَ التركيب. تُضيف للفئات تعليقات توضيحية كـ @Service و@Repository و@Component؛ يفحصها Spring وينشئها بالترتيب الصحيح ويحقن تبعياتها. نادرًا ما تكتب كود الربط يدويًا.
الخلاصة
حقن التبعيات يعني تزويد الفئة بالكائنات التي تحتاجها بدلًا من السماح لها بإنشائها. الآليات الثلاث — حقن المُنشئ والمُعيِّن والحقل — تحقق هذا كلها، لكن حقن المُنشئ مفضّل بشدة لأنه ينتج فئات غير قابلة للتغيير وقابلة للاختبار وموثّقة ذاتيًا. يُطبِّق DI مبدأ انعكاس التبعيات، ويفصل منطق الأعمال عن البنية التحتية، وهو الأساس الذي يقوم عليه نظام Spring البيئي بأكمله. في الدرس القادم ستُطبّق حقن المُنشئ بعمق، بما في ذلك كيفية حلّ Spring لمعاملات المُنشئ وتلبيتها تلقائيًا.