أساسيات Dependency Injection
جزء من قسم Object Oriented Programming في رحلة C# على Coddy — الدرس 46 من 70.
حقن التبعية (Dependency Injection - DI) هو تقنية تستقبل فيها الفئة (class) تبعياتها من الخارج بدلاً من إنشائها بنفسها. وهذا يجعل الكود أكثر مرونة، وقابلية للاختبار، وضعيف الاقتران (loosely coupled).
بدون DI، يقوم الـ class بإنشاء تبعياته الخاصة، مما يجعل من الصعب تغييره أو اختباره:
public class OrderService
{
private EmailSender sender = new EmailSender(); // مقترن بشكل وثيق
public void PlaceOrder() => sender.Send("Order placed");
}مع حقن التبعية (DI)، يتم تمرير التبعيات من خلال المنشئ (constructor):
public interface IMessageSender
{
void Send(string message);
}
public class EmailSender : IMessageSender
{
public void Send(string message) => Console.WriteLine("Email: " + message);
}
public class OrderService
{
private IMessageSender sender;
public OrderService(IMessageSender sender)
{
this.sender = sender;
}
public void PlaceOrder() => sender.Send("Order placed");
}الآن لا تعرف OrderService ولا تهتم ما إذا كانت تستخدم البريد الإلكتروني، أو الرسائل القصيرة (SMS)، أو مرسلًا وهميًا (mock sender) للاختبار. يتم "حقن" التبعية عند إنشاء الكائن:
IMessageSender emailSender = new EmailSender();
OrderService service = new OrderService(emailSender);
service.PlaceOrder(); // Email: Order placedيتبع هذا النمط مبدأ الاعتماد على التجريدات (interfaces) بدلاً من التنفيذات الملموسة (concrete implementations). يتيح ذلك تبديل التنفيذات دون تعديل الـ class الذي يستخدمها، وهو أمر ضروري لـ unit testing وللحفاظ على معماريات مرنة.
التحدي
سهللنقم ببناء نظام تسجيل (logging) يوضح قوة حقن التبعية (dependency injection). بدلاً من كتابة كيفية تسجيل السجلات بشكل ثابت، ستقوم بإنشاء فئة Logger مرنة يمكنها العمل مع أي وجهة تسجيل - سواء كانت وحدة التحكم (console)، أو محاكاة ملف، أو أي شيء آخر - دون معرفة التفاصيل الدقيقة.
ستقوم بتنظيم الكود الخاص بك عبر ثلاثة ملفات:
ILogWriter.cs: قم بتعريف واجهة (interface) تسمىILogWriterداخل مساحة الأسماءLogging. تمثل هذه الواجهة العقد لأي وجهة تسجيل ويجب أن تحتوي على طريقة واحدةWrite(string message)تعيد سلسلة نصية تصف المكان الذي تمت كتابة الرسالة فيه.LogWriters.cs: قم بإنشاء تطبيقين ملموسين لـILogWriterداخل مساحة الأسماءLogging:ConsoleLogWriter- تعيد طريقةWriteالخاصة به"Console: " + messageFileLogWriter- يأخذ اسم ملف (filename) من خلال منشئه (constructor) وتعيد طريقةWriteالخاصة به"File[" + filename + "]: " + message
Logger.cs: قم بإنشاء فئةLoggerفي مساحة الأسماءLoggingتستقبل تبعيتها من خلال حقن المنشئ (constructor injection). يجب علىLogger:- قبول
ILogWriterمن خلال المنشئ الخاص به وتخزينه في حقل خاص (private field) - امتلاك طريقة
Log(string message)تفوض العمل إلى الكاتب المحقون (injected writer) وتعيد النتيجة
- قبول
Program.cs: قم بتوضيح حقن التبعية من خلال إنشاء مسجلين مختلفين - أحدهما باستخدامConsoleLogWriterوالآخر باستخدامFileLogWriter. أظهر كيف تتصرف نفس فئةLoggerبشكل مختلف بناءً على التبعية التي تم حقنها.
ستتلقى مدخلين:
- اسم الملف لمسجل الملفات (file logger)
- الرسالة المراد تسجيلها
قم بإنشاء Logger باستخدام ConsoleLogWriter وقم بتسجيل الرسالة. ثم قم بإنشاء Logger آخر باستخدام FileLogWriter (باستخدام اسم الملف من المدخلات) وقم بتسجيل نفس الرسالة. اطبع كل نتيجة في سطر مستقل.
على سبيل المثال، إذا كانت المدخلات هي app.log و System started، يجب أن يكون الناتج:
Console: System started
File[app.log]: System startedلاحظ كيف أن فئة Logger لا تتغير أبداً - فهي تعمل مع أي تطبيق لـ ILogWriter. هذا هو جوهر حقن التبعية: تعتمد فئة Logger على تجريد (الواجهة)، ويتم "حقن" التطبيق الملموس من الخارج. يمكنك بسهولة إضافة DatabaseLogWriter أو CloudLogWriter لاحقاً دون تعديل فئة Logger على الإطلاق!
ورقة مرجعية
حقن التبعية (Dependency Injection - DI) هو تقنية تتلقى فيها الفئة (class) تبعياتها من الخارج بدلاً من إنشائها بنفسها، مما يجعل الكود أكثر مرونة، وقابلية للاختبار، وفضفاض الارتباط (loosely coupled).
بدون حقن التبعية (ارتباط وثيق - tightly coupled):
public class OrderService
{
private EmailSender sender = new EmailSender(); // Tightly coupled
public void PlaceOrder() => sender.Send("Order placed");
}مع حقن التبعية (ارتباط فضفاض - loosely coupled):
public interface IMessageSender
{
void Send(string message);
}
public class EmailSender : IMessageSender
{
public void Send(string message) => Console.WriteLine("Email: " + message);
}
public class OrderService
{
private IMessageSender sender;
public OrderService(IMessageSender sender)
{
this.sender = sender;
}
public void PlaceOrder() => sender.Send("Order placed");
}حقن التبعية:
IMessageSender emailSender = new EmailSender();
OrderService service = new OrderService(emailSender);
service.PlaceOrder(); // Email: Order placedيتبع حقن التبعية مبدأ الاعتماد على التجريدات (interfaces) بدلاً من التنفيذات الملموسة (concrete implementations)، مما يتيح تبديل التنفيذات دون تعديل الفئة التي تستخدمها.
جرّب بنفسك
using System;
using Logging;
class Program
{
public static void Main(string[] args)
{
// قراءة المدخلات
string filename = Console.ReadLine();
string message = Console.ReadLine();
// TODO: قم بإنشاء Logger باستخدام ConsoleLogWriter وسجل الرسالة
// طباعة النتيجة
// TODO: قم بإنشاء Logger آخر باستخدام FileLogWriter (باستخدام filename) وسجل نفس الرسالة
// طباعة النتيجة
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات OOP
الملفات الخارجيةNamespaces والتوجيهاتمقدمة في Classes و Objectsالكلمة المفتاحية 'this'Methods والمعاملاتFields مقابل PropertiesConstructorsObject Initializersمراجعة - آلة حاسبة بسيطة4الوراثة
صيغة الوراثة الأساسية (:)الكلمة المفتاحية 'base'الكلمات المفتاحية Virtual و Overrideالأصناف المختومة (Sealed Classes)الفئة الأساسية 'object'مراجعة - هيكلية الموظفين7ميزات متقدمة
تحميل العوامل (Operator Overloading)المفهرسات (Indexers (this[]))إعادة تعريف ()ToStringدوال التوسعة (Extension Methods)ملخص - Custom List2الخصائص والأعضاء الساكنة (Static Members)
الخصائص المطبقة تلقائيًا (Auto-Implemented Properties)خصائص القراءة فقط والكتابة فقطالحقول والدوال الساكنة (Static)الفئات الساكنة (Static Classes)الأعضاء ذات البنية التعبيرية (Expression-Bodied Members)5تعدد الأشكال والواجهات (Polymorphism & Interfaces)
تعدد الأشكال: وقت التجميع مقابل وقت التشغيلالواجهة مقابل الفئة المجردة (Abstract Class)الواجهات المتعددةالواجهات الصريحةUpcasting و Downcastingملخص - حاسبة الأشكال8مفاهيم البرمجة كائنية التوجه (OOP) المتقدمة
التركيب بدلاً من الوراثةالـ Generics (الأصناف والدوال)الـ Delegates والأحداثالـ Attributes والـ Reflectionواجهة IDisposable وجملة usingأساسيات Dependency Injection