مقدمة: ما هو حقن التبعية ولماذا هو ضروري
\nحقن التبعية (DI) هو أحد أنماط التصميم الرئيسية التي تطبق مبدأ عكس التبعيات (Dependency Inversion Principle) من SOLID. عملياً، هذا يعني أن الكائنات لا تنشئ تبعياتها بنفسها، بل تحصل عليها من الخارج. وهذا يجعل الكود مرناً، قابلاً للاختبار، وذا اقتران ضعيف.
\nفي نظام C# و .NET، أصبح DI هو المعيار الفعلي. بدءاً من .NET Core، تحتوي المنصة على حاوية حقن تبعيات مدمجة تُستخدم في ASP.NET Core والخدمات الخلفية وحتى تطبيقات الكونسول. بدون فهم DI، لا يمكن تصور التطوير الحديث باستخدام C#.
\nفي هذه المقالة، سنشرح DI بأمثلة واقعية: من الحقن اليدوي إلى استخدام الحاوية المدمجة في .NET. سترى كيف تتخلص من الروابط الصلبة وتجعل الكود مناسباً للاختبارات الوحدوية.
\n\nالمشكلة بدون حقن التبعية
\nلنتخيل سيناريو نموذجياً: فئة OrderService تعالج الطلبات وترسل إشعارات البريد الإلكتروني. بدون DI، يبدو الكود كالتالي:
public class EmailSender\n{\n public void Send(string to, string subject, string body)\n {\n Console.WriteLine($"إرسال بريد إلكتروني إلى {to}: {subject}");\n }\n}\n\npublic class OrderService\n{\n private EmailSender _emailSender;\n\n public OrderService()\n {\n // ارتباط صلب - الفئة تنشئ التبعية بنفسها\n _emailSender = new EmailSender();\n }\n\n public void ProcessOrder(int orderId)\n {\n // منطق معالجة الطلب...\n _emailSender.Send("user@example.com", "تم تقديم الطلب", $"طلبك رقم #{orderId} تم استلامه.");\n }\n}\nما المشكلة؟ OrderService مرتبط بشكل صلب بتنفيذ محدد لـ EmailSender. إذا أردنا استبدال إرسال البريد الإلكتروني بـ SMS أو تسجيل، فسنضطر لتعديل كود OrderService. كما يصبح الاختبار أكثر صعوبة: لا يمكن استبدال EmailSender الحقيقي بكائن وهمي (mock).
الحل: حقن التبعيات عبر المُنشئ
\nالخطوة الأولى نحو DI هي استخراج تجريد (واجهة) وتمرير التبعية عبر المُنشئ. يُسمى هذا حقن المُنشئ (Constructor Injection).
\n\nالخطوة 1: إنشاء واجهة
\npublic interface INotificationService\n{\n void Send(string to, string subject, string body);\n}\n\nالخطوة 2: تنفيذ الواجهة
\npublic class EmailSender : INotificationService\n{\n public void Send(string to, string subject, string body)\n {\n Console.WriteLine($"تم إرسال البريد الإلكتروني: {subject} -> {to}");\n }\n}\n\npublic class SmsSender : INotificationService\n{\n public void Send(string to, string subject, string body)\n {\n Console.WriteLine($"تم إرسال SMS: {subject} -> {to}");\n }\n}\n\nالخطوة 3: حقن التبعية عبر المُنشئ
\npublic class OrderService\n{\n private readonly INotificationService _notificationService;\n\n // يتم تمرير التبعية من الخارج\n public OrderService(INotificationService notificationService)\n {\n _notificationService = notificationService;\n }\n\n public void ProcessOrder(int orderId)\n {\n // منطق معالجة الطلب...\n _notificationService.Send("user@example.com", "تم تقديم الطلب", $"طلبك رقم #{orderId} تم استلامه.");\n }\n}\nالآن OrderService لا يعرف أي تنفيذ محدد يُستخدم. يمكننا توفير EmailSender أو SmsSender أو أي فئة أخرى تنفذ INotificationService. أصبح الكود مرناً وقابلاً للاختبار.
استخدام حاوية IoC المدمجة في .NET
\nإنشاء جميع التبعيات يدوياً ليس مريحاً. في .NET توجد حاوية مدمجة (حاوية IoC) تدير دورة حياة الكائنات تلقائياً. لنلقِ نظرة على مثال لتطبيق كونسول.
\n\n