وابستگی متقابل (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)، داتنت نمیتواند هیچکدام از این دو کلاس را نمونهسازی کند و خطای دایرهای رخ میدهد.
سریعترین راه برای شکستن این حلقه (بدون تغییر دادن معماری و منطق برنامه)، به تعویق انداختن زمان دریافت وابستگی است.
به جای اینکه در زمان ساخت کلاس (Constructor) سرویس مورد نظر را درخواست کنیم، ظرف (Container) داتنت یعنی IServiceProvider را تزریق میکنیم و سرویس را فقط در زمان اجرای متد (Method Execution) بیرون میکشیم.
در اینجا یکی از سرویسها (مثلاً 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 (که همیشه در دسترس است) نیاز دارد. پس هر دو کلاس با موفقیت ساخته میشوند و حلقه میشکند.
اگرچه استفاده از 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 (بازنویسی ساختار) دارید، حتماً با استخراج سرویسهای مشترک، معماری لایهها را تمیزتر کنید تا از این خطاها در آینده پیشگیری شود.
0 نظر
هنوز نظری برای این مقاله ثبت نشده است.