پادشاهِ کُدنویسا شو!
کینگتو - آموزش برنامه نویسی تخصصصی - دات نت - سی شارپ - بانک اطلاعاتی و امنیت

بررسی وابستگی حلقوی (Circular Dependency) و مدیریت آن در دات‌نت

8 بازدید 0 نظر ۱۴۰۵/۰۳/۱۵
در معماری نرم‌افزار، کلاس‌ها برای انجام وظایف خود به سرویس‌های دیگر نیاز دارند. زمانی که این نیازها به صورت چرخشی در بیایند، با مشکل وابستگی حلقوی (Circular Dependency) مواجه می‌شویم.

وابستگی حلقوی و وابستگی متقابل چیست؟

وابستگی متقابل (Cross-Dependency) حالت خاصی از این مشکل است که در آن دو کلاس مستقیماً به یکدیگر وابسته‌اند. به زبان ساده: کلاس A برای ساخته شدن به کلاس B نیاز دارد، و کلاس B برای ساخته شدن به کلاس A نیاز دارد!

موتور تزریق وابستگی (DI Container) در فریم‌ورک‌هایی مثل ASP.NET Core، اشیاء را از طریق سازنده (Constructor) می‌سازد. وقتی با این سناریو مواجه می‌شود، در یک حلقه بی‌نهایت گیر می‌افتد (می‌خواهد A را بسازد، می‌بیند B لازم است؛ می‌رود B را بسازد، می‌بیند A لازم است...) و در نهایت با پرتاب خطای InvalidOperationException برنامه را متوقف می‌کند.

 

بررسی یک سناریوی مخرب (مثال اشتباه)

فرض کنید دو سرویس به نام‌های OrderService (مدیریت سفارشات) و NotificationService (ارسال پیامک) داریم.

  • وقتی سفارشی ثبت می‌شود، OrderService می‌خواهد از طریق NotificationService پیامک بفرستد.

  • وقتی پیامکی با موفقیت ارسال می‌شود، NotificationService می‌خواهد از طریق OrderService وضعیت سفارش را به‌روز کند.

❌ کد اشتباه (طراحی دارای وابستگی متقابل)

// سرویس سفارشات
public class OrderService : IOrderService
{
    private readonly INotificationService _notificationService;

    public OrderService(INotificationService notificationService)
    {
        _notificationService = notificationService; // وابستگی به نوتیفیکیشن
    }

    public void PlaceOrder()
    {
        // ثبت سفارش...
        _notificationService.SendSms("سفارش ثبت شد.");
    }
}

// سرویس پیامک
public class NotificationService : INotificationService
{
    private readonly IOrderService _orderService;

    public OrderService(IOrderService orderService)
    {
        _orderService = orderService; // وابستگی به سفارشات (بروز حلقه!)
    }

    public void SendSms(string message)
    {
        // ارسال پیامک...
        _orderService.UpdateOrderStatus("پیامک ارسال شد"); 
    }
}

نتیجه: هنگام بالا آمدن برنامه (Startup)، دات‌نت نمی‌تواند هیچ‌کدام از این دو کلاس را نمونه‌سازی کند و خطای دایره‌ای رخ می‌دهد.

راه‌حل سریع (Quick Fix): استفاده از IServiceProvider

سریع‌ترین راه برای شکستن این حلقه (بدون تغییر دادن معماری و منطق برنامه)، به تعویق انداختن زمان دریافت وابستگی است.

به جای اینکه در زمان ساخت کلاس (Constructor) سرویس مورد نظر را درخواست کنیم، ظرف (Container) دات‌نت یعنی IServiceProvider را تزریق می‌کنیم و سرویس را فقط در زمان اجرای متد (Method Execution) بیرون می‌کشیم.

✅ کد درست (راه‌حل سریع با IServiceProvider)

در اینجا یکی از سرویس‌ها (مثلاً NotificationService) را تغییر می‌دهیم تا حلقه بشکند:

using Microsoft.Extensions.DependencyInjection;

public class NotificationService : INotificationService
{
    private readonly IServiceProvider _serviceProvider;

    // تزریق IServiceProvider به جای سرویس مستقیم
    public NotificationService(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public void SendSms(string message)
    {
        // ارسال پیامک...
        
        // دریافت سرویسِ سفارشات دقیقاً در لحظه نیاز
        var orderService = _serviceProvider.GetRequiredService<IOrderService>();
        orderService.UpdateOrderStatus("پیامک ارسال شد");
    }
}

چرا این کار می‌کند؟

وقتی دات‌نت می‌خواهد OrderService را بسازد، به NotificationService نیاز دارد. برای ساخت NotificationService دیگر نیازی به OrderService ندارد، بلکه فقط به IServiceProvider (که همیشه در دسترس است) نیاز دارد. پس هر دو کلاس با موفقیت ساخته می‌شوند و حلقه می‌شکند.

 

راه‌حل اصولی (Best Practice): اصلاح معماری

اگرچه استفاده از IServiceProvider مشکل را سریعاً حل می‌کند، اما از نظر معماری نرم‌افزار، الگوی Service Locator (ضد الگو) محسوب می‌شود، زیرا وابستگی‌های کلاس را در سازنده پنهان می‌کند و تست‌نویسی (Unit Testing) را سخت‌تر می‌سازد.

راه‌حل اصولی، استخراج منطق مشترک به یک کلاس/سرویس سوم است.

✅ کد درست (راه‌حل معماری)

ما یک سرویس سوم به نام OrderUpdateService می‌سازیم که وظیفه‌اش فقط آپدیت وضعیت سفارش است. حالا هر دو سرویس قبلی به این سرویس جدید وابسته می‌شوند و هیچ‌کدام به یکدیگر کاری ندارند.

// 1. سرویس مشترک جدید
public class OrderUpdateService : IOrderUpdateService
{
    public void UpdateStatus(string message)
    {
        // منطق بروزرسانی دیتابیس
    }
}

// 2. سرویس سفارشات (وابسته به سرویس نوتیفیکیشن)
public class OrderService : IOrderService
{
    private readonly INotificationService _notificationService;

    public OrderService(INotificationService notificationService)
    {
        _notificationService = notificationService;
    }
    // ...
}

// 3. سرویس نوتیفیکیشن (وابسته به سرویس مشترک به جای سرویس سفارشات)
public class NotificationService : INotificationService
{
    private readonly IOrderUpdateService _orderUpdateService;

    public NotificationService(IOrderUpdateService orderUpdateService)
    {
        _orderUpdateService = orderUpdateService; // حلقه شکسته شد!
    }

    public void SendSms(string message)
    {
        // ارسال پیامک...
        _orderUpdateService.UpdateStatus("پیامک ارسال شد");
    }
}

 

جمع‌بندی

  • وابستگی حلقوی نشانه‌ای از درهم‌تنیدگی بیش از حد (High Coupling) در کد است.

  • اگر در وسط تحویل پروژه هستید و نیاز به یک راه‌حل اورژانسی دارید، استفاده از IServiceProvider کاملاً منطقی و راهگشاست.

  • اگر در مرحله طراحی هستید یا زمان کافی برای Refactoring (بازنویسی ساختار) دارید، حتماً با استخراج سرویس‌های مشترک، معماری لایه‌ها را تمیزتر کنید تا از این خطاها در آینده پیشگیری شود.

 
لینک استاندارد شده: W2jtsI7J5B

0 نظر

    هنوز نظری برای این مقاله ثبت نشده است.
جستجوی مقاله و آموزش
دوره‌ها با تخفیفات ویژه