مقدمة: مشكلة الاقتران القوي في جافا
\n\nتخيل أنك تكتب تطبيقًا لإرسال الإشعارات. في صف NotificationService الخاص بك، يتم إنشاء كائن EmailSender مباشرة. كل شيء يعمل بشكل رائع، حتى يطلب منك العميل يومًا ما إضافة دعم إشعارات SMS و Push. سيتعين عليك إعادة كتابة NotificationService، وتغيير المُنشئات، وإضافة شروط جديدة. يصبح الكود هشًا، ويصعب اختباره وتوسيعه.
هذه هي المشكلة الكلاسيكية للـ الاقتران القوي (tight coupling). الحل يسمى Dependency Injection (حقن التبعية) — وهو أحد المبادئ الأساسية للتصميم في جافا وأساس أطر العمل مثل Spring. في هذه المقالة، سنشرح ما هو Dependency Injection بكلمات بسيطة، وكيفية تنفيذه، ولماذا لا يمكن لأي مطور جافا محترف الاستغناء عنه.
\n\nما هو Dependency Injection بكلمات بسيطة؟
\n\nDependency Injection (DI) هو نمط تصميم يحصل فيه الكائن على تبعياته من الخارج، بدلاً من إنشائها بنفسه. بدلاً من كتابة new EmailSender() داخل الصف، فإنك «تحقن» كائنًا جاهزًا عبر المُنشئ، أو setter، أو واجهة.
يرتبط DI ارتباطًا وثيقًا بمبدأ Inversion of Control (IoC) — عكس التحكم. إذا كان صفك في السابق يدير إنشاء التبعيات بنفسه، فإن التحكم يُنقل الآن إلى حاوية خارجية (مثل Spring IoC).
\n\nالمزايا الرئيسية لـ DI:
\n- \n
- الاقتران الضعيف (loose coupling) — تعتمد الصفوف على التجريدات، وليس على التطبيقات الملموسة. \n
- تبسيط الاختبار — يمكن استبدال التبعيات بسهولة بـ mocks أو stubs. \n
- إعادة استخدام الكود — يمكن استخدام نفس الخدمة في سياقات مختلفة. \n
- سهولة الصيانة — تغيير التطبيق لا يتطلب تعديل الصفوف المستهلكة. \n
أنواع حقن التبعية في جافا
\n\nهناك ثلاث طرق رئيسية لتنفيذ Dependency Injection في جافا. دعنا ننظر إلى كل منها بمثال.
\n\n1. الحقن عبر المُنشئ (Constructor Injection)
\n\nالطريقة الأكثر شيوعًا والموصى بها. يتم تمرير التبعيات عبر معاملات المُنشئ وتُحفظ كحقول للصف (عادةً final).
// واجهة لإرسال الرسائل\npublic interface MessageSender {\n void send(String message, String recipient);\n}\n\n// تطبيق ملموس — Email\npublic class EmailSender implements MessageSender {\n @Override\n public void send(String message, String recipient) {\n System.out.println("إرسال بريد إلكتروني: " + message + " إلى " + recipient);\n }\n}\n\n// خدمة تستخدم DI عبر المُنشئ\npublic class NotificationService {\n private final MessageSender sender;\n\n // يتم حقن التبعية عبر المُنشئ\n public NotificationService(MessageSender sender) {\n this.sender = sender;\n }\n\n public void notifyUser(String message, String userEmail) {\n sender.send(message, userEmail);\n }\n}\n\n// الاستخدام\npublic class Main {\n public static void main(String[] args) {\n MessageSender emailSender = new EmailSender();\n NotificationService service = new NotificationService(emailSender);\n service.notifyUser("مرحبًا بك!", "user@example.com");\n }\n}\n\nالإيجابيات: يتم إنشاء الكائن في حالة جاهزة تمامًا، والتبعيات غير قابلة للتغيير (immutable)، وسهلة الاختبار.
\n\n2. الحقن عبر setter (Setter Injection)
\n\nيتم تمرير التبعيات عبر دوال setter بعد إنشاء الكائن. يُستخدم غالبًا للتبعيات الاختيارية.
\n\npublic class NotificationService {\n private MessageSender sender;\n\n // دالة setter لحقن التبعية\n public void setSende