Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

أساسيات 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 وللحفاظ على معماريات مرنة.

challenge icon

التحدي

سهل

لنقم ببناء نظام تسجيل (logging) يوضح قوة حقن التبعية (dependency injection). بدلاً من كتابة كيفية تسجيل السجلات بشكل ثابت، ستقوم بإنشاء فئة Logger مرنة يمكنها العمل مع أي وجهة تسجيل - سواء كانت وحدة التحكم (console)، أو محاكاة ملف، أو أي شيء آخر - دون معرفة التفاصيل الدقيقة.

ستقوم بتنظيم الكود الخاص بك عبر ثلاثة ملفات:

  • ILogWriter.cs: قم بتعريف واجهة (interface) تسمى ILogWriter داخل مساحة الأسماء Logging. تمثل هذه الواجهة العقد لأي وجهة تسجيل ويجب أن تحتوي على طريقة واحدة Write(string message) تعيد سلسلة نصية تصف المكان الذي تمت كتابة الرسالة فيه.
  • LogWriters.cs: قم بإنشاء تطبيقين ملموسين لـ ILogWriter داخل مساحة الأسماء Logging:
    • ConsoleLogWriter - تعيد طريقة Write الخاصة به "Console: " + message
    • FileLogWriter - يأخذ اسم ملف (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) وسجل نفس الرسالة
        // طباعة النتيجة
    }
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming