در دنیای پیچیده توسعه نرم‌افزار، ساخت برنامه‌هایی که نه تنها به خوبی کار می‌کنند بلکه در طول زمان نیز قابل نگهداری، تست و گسترش باشند، از اهمیت بالایی برخوردار است. یکی از الگوهای طراحی قدرتمند که به دستیابی به این اهداف کمک می‌کند، الگوی تزریق وابستگی (Dependency Injection) است. این الگو، که به عنوان یک اصل کلیدی در برنامه‌نویسی شیءگرا مدرن شناخته می‌شود، نحوه مدیریت وابستگی‌های بین اجزای مختلف یک نرم‌افزار را متحول می‌سازد. در این مقاله، به بررسی عمیق مفهوم تزریق وابستگی در دات نت، دلایل اهمیت آن، مزایای استفاده و نحوه پیاده‌سازی آن خواهیم پرداخت.
کینگتو - آموزش برنامه نویسی تخصصصی - دات نت - سی شارپ - بانک اطلاعاتی و امنیت

تزریق وابستگی و یا Dependency Injection چیست؟ راهی برای ساخت نرم‌افزارهای انعطاف‌پذیر و قابل نگهداری

93 بازدید 0 نظر ۱۴۰۴/۰۲/۰۸

چیستی تزریق وابستگی؟
در ساده‌ترین تعریف، تزریق وابستگی یک تکنیک طراحی است که در آن وابستگی‌های یک شیء (یعنی اشیاء دیگری که برای انجام وظایف خود به آن‌ها نیاز دارد) به جای اینکه در داخل خود شیء ایجاد شوند، از خارج به آن تزریق می‌شوند. به عبارت دیگر، یک کلاس نباید مسئول ایجاد نمونه‌های وابستگی‌های خود باشد. این مسئولیت به یک موجودیت خارجی (معمولاً یک کانتینر تزریق وابستگی یا یک سازنده) واگذار می‌شود.

برای درک بهتر این مفهوم، یک مثال ساده را در نظر بگیرید. فرض کنید یک کلاس UserService داریم که برای ارسال ایمیل به کاربران به یک کلاس EmailService نیاز دارد. بدون استفاده از تزریق وابستگی، کلاس UserService ممکن است به صورت مستقیم یک نمونه از EmailService را در داخل خود ایجاد کند:

public class UserService
{
    private readonly EmailService _emailService;

    public UserService()
    {
        _emailService = new EmailService();
    }

    public void RegisterUser(string email, string password)
    {
        _emailService.SendWelcomeEmail(email);
    }
}

public class EmailService
{
    public void SendWelcomeEmail(string email)
    {
    }
}


در این رویکرد، کلاس UserService به طور مستقیم به پیاده‌سازی خاص EmailService وابسته است. اگر بخواهیم در آینده یک پیاده‌سازی متفاوت از سرویس ایمیل (مثلاً SmsService) را جایگزین کنیم، باید کد UserService را تغییر دهیم. این امر منجر به وابستگی تنگاتنگ بین کلاس‌ها می‌شود که انعطاف‌پذیری و قابلیت نگهداری کد را کاهش می‌دهد.

با استفاده از تزریق وابستگی، این وابستگی از خارج به کلاس UserService تزریق می‌شود. معمولاً این کار از طریق سازنده (Constructor Injection) انجام می‌شود:

 

public class UserService
{
    private readonly IEmailService _emailService;

    public UserService(IEmailService emailService)
    {
        _emailService = emailService ?? throw new ArgumentNullException(nameof(emailService));
    }

    public void RegisterUser(string email, string password)
    {
        _emailService.SendWelcomeEmail(email);
    }
}

public interface IEmailService
{
    void SendWelcomeEmail(string email);
}

public class EmailService : IEmailService
{
    public void SendWelcomeEmail(string email)
    {
    }
}

public class SmsService : IEmailService
{
    public void SendWelcomeEmail(string email)
    {
    }
}

IEmailService emailService = new EmailService();
UserService userService = new UserService(emailService);


در این مثال، کلاس UserService به یک انتزاع (IEmailService) وابسته است، نه یک پیاده‌سازی خاص (EmailService). مسئولیت ایجاد و ارائه یک نمونه از IEmailService به کلاس UserService واگذار شده است. این امر امکان جایگزینی آسان پیاده‌سازی‌های مختلف IEmailService را بدون تغییر در کد UserService فراهم می‌کند.

چرایی اهمیت تزریق وابستگی؟
استفاده از تزریق وابستگی مزایای متعددی برای توسعه نرم‌افزار به همراه دارد که در ادامه به برخی از مهم‌ترین آن‌ها اشاره می‌شود:

  • کاهش وابستگی: تزریق وابستگی با جدا کردن کلاس‌ها از پیاده‌سازی‌های خاص وابستگی‌هایشان، وابستگی بین آن‌ها را کاهش می‌دهد. این امر منجر به ایجاد سیستم‌های با کوپلینگ سست (Loosely Coupled) می‌شود که تغییر و نگهداری آن‌ها آسان‌تر است.
  • افزایش قابلیت تست: وقتی وابستگی‌ها از خارج تزریق می‌شوند، می‌توان به راحتی وابستگی‌های واقعی را با اشیاء ساختگی (Mock Objects) یا استاب‌ها (Stubs) در طول تست واحد جایگزین کرد. این امر امکان تست ایزوله و کارآمد اجزای مختلف نرم‌افزار را فراهم می‌کند. در مثال بالا، می‌توان در تست‌های UserService یک پیاده‌سازی ساختگی از IEmailService را تزریق کرد و رفتار آن را کنترل نمود.
  • بهبود قابلیت نگهداری: سیستم‌های با وابستگی کمتر، درک و تغییر دادن آن‌ها آسان‌تر است. تغییر در یک جزء به احتمال کمتری بر سایر اجزاء تأثیر می‌گذارد. این امر نگهداری و رفع اشکالات نرم‌افزار را ساده‌تر می‌کند.
  • افزایش قابلیت گسترش: تزریق وابستگی امکان افزودن ویژگی‌ها و قابلیت‌های جدید به نرم‌افزار را بدون ایجاد تغییرات گسترده در کد موجود فراهم می‌کند. می‌توان پیاده‌سازی‌های جدیدی از رابط‌ها ایجاد کرده و آن‌ها را به اجزای مختلف تزریق نمود.
  • افزایش قابلیت استفاده مجدد کد: کلاس‌هایی که به انتزاع‌ها وابسته هستند، نسبت به کلاس‌هایی که به پیاده‌سازی‌های خاص وابسته هستند، قابلیت استفاده مجدد بیشتری دارند. می‌توان از آن‌ها در زمینه‌های مختلف با تزریق پیاده‌سازی‌های متفاوت استفاده کرد.

 

پیروی از اصل وارونگی وابستگی (Dependency Inversion Principle - DIP):

تزریق وابستگی یکی از راهکارهای پیاده‌سازی اصل DIP است. این اصل بیان می‌کند که:

ماژول‌های سطح بالا نباید به ماژول‌های سطح پایین وابسته باشند. هر دو باید به انتزاع‌ها وابسته باشند.
انتزاع‌ها نباید به جزئیات وابسته باشند. جزئیات باید به انتزاع‌ها وابسته باشند.
تزریق وابستگی با وادار کردن کلاس‌ها به وابستگی به انتزاع‌ها (مانند رابط‌ها یا کلاس‌های انتزاعی) به جای پیاده‌سازی‌های خاص، به تحقق این اصل کمک می‌کند.

 

چگونگی تزریق وابستگی در دات نت؟
در دات نت، روش‌های مختلفی برای پیاده‌سازی تزریق وابستگی وجود دارد:

تزریق سازنده (Constructor Injection): این رایج‌ترین روش تزریق وابستگی است. در این روش، وابستگی‌های یک کلاس از طریق پارامترهای سازنده آن به کلاس ارائه می‌شوند. مثال UserService و IEmailService که قبلاً ذکر شد، نمونه‌ای از تزریق سازنده است. این روش تضمین می‌کند که یک شیء تنها در صورتی می‌تواند ایجاد شود که تمام وابستگی‌های مورد نیازش فراهم شده باشند.

تزریق ویژگی (Property Injection) یا تزریق تنظیم‌کننده (Setter Injection): در این روش، وابستگی‌ها از طریق ویژگی‌های عمومی کلاس یا متدهای تنظیم‌کننده (setter methods) پس از ایجاد شیء به آن تزریق می‌شوند. این روش انعطاف‌پذیری بیشتری را در اختیار قرار می‌دهد، زیرا وابستگی‌ها می‌توانند اختیاری باشند. با این حال، ممکن است منجر به ایجاد اشیائی شود که در حالت نامعتبر قرار دارند (یعنی وابستگی‌های مورد نیازشان هنوز تنظیم نشده‌اند).

public class ReportGenerator
{
    public ILogger Logger { get; set; } 

    public ReportGenerator()
    {
        // ...
    }

    public void GenerateReport(string data)
    {
        Logger?.LogInformation($"Generating report with data: {data}");
    }
}

ReportGenerator generator = new ReportGenerator();
generator.Logger = new FileLogger("report.log");


تزریق متد (Method Injection): در این روش، وابستگی‌ها به عنوان پارامترهای یک متد در کلاس تزریق می‌شوند. این روش زمانی مفید است که یک وابستگی فقط در یک متد خاص مورد نیاز باشد.

 

 

public class DataProcessor
{
    public void ProcessData(string input, IDataValidator validator)
    {
        if (validator.IsValid(input))
        {
          // process
        }
        else
        {
           // error managements
        }
    }
}
DataProcessor processor = new DataProcessor();
IDataValidator stringValidator = new StringLengthValidator(100);
processor.ProcessData("some data", stringValidator);


کانتینرهای تزریق وابستگی در دات نت:
در برنامه‌های بزرگ و پیچیده، مدیریت دستی وابستگی‌ها می‌تواند دشوار و زمان‌بر شود. کانتینرهای تزریق وابستگی (Dependency Injection Containers) یا کانتینرهای IoC (Inversion of Control Containers) ابزارهایی هستند که فرآیند ایجاد و مدیریت وابستگی‌ها را به صورت خودکار انجام می‌دهند.

کانتینر تزریق وابستگی مسئولیت‌های زیر را بر عهده دارد:

ثبت (Registration): پیکربندی نگاشت بین انتزاع‌ها (مانند رابط‌ها) و پیاده‌سازی‌هایConcrete آن‌ها.
حل (Resolution): ایجاد نمونه‌های کلاس‌ها و تزریق وابستگی‌های آن‌ها به طور خودکار.
مدیریت چرخه حیات (Lifetime Management): کنترل نحوه ایجاد و از بین رفتن نمونه‌های وابستگی‌ها (مانند Singleton، Scoped، Transient).
دات نت به طور پیش‌فرض یک کانتینر تزریق وابستگی سبک و داخلی را ارائه می‌دهد که در ASP.NET Core به طور گسترده مورد استفاده قرار می‌گیرد. می‌توان از طریق متد ConfigureServices در کلاس Startup سرویس‌ها (وابستگی‌ها) را ثبت کرد.

 

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient();
    services.AddScoped();
    services.AddSingleton();
    services.AddControllers();
}


در این مثال:

AddTransient: یک نمونه جدید از EmailService هر بار که IEmailService درخواست شود، ایجاد می‌کند.
AddScoped: یک نمونه از ApplicationDbContext در طول یک درخواست (مانند یک درخواست HTTP در ASP.NET Core) ایجاد می‌شود و برای تمام درخواست‌های درون همان Scope یکسان است.
AddSingleton: یک نمونه واحد از LoggerFactory در طول عمر برنامه ایجاد می‌شود و برای تمام درخواست‌ها به اشتراک گذاشته می‌شود.
پس از ثبت سرویس‌ها، می‌توان آن‌ها را در سازنده‌های کنترلرها، سرویس‌ها و سایر کلاس‌های مدیریت شده توسط کانتینر تزریق وابستگی، درخواست کرد. فریمورک دات نت به طور خودکار نمونه‌های مورد نیاز را ایجاد و تزریق می‌کند.

علاوه بر کانتینر داخلی دات نت، کانتینرهای تزریق وابستگی قدرتمند و محبوب دیگری نیز برای دات نت وجود دارند، از جمله Autofac، Ninject، StructureMap و Unity که امکانات پیشرفته‌تری را برای مدیریت وابستگی‌ها فراهم می‌کنند.

اهمیت تزریق وابستگی در NET CORE.
در معماری ASP.NET Core، تزریق وابستگی نقش محوری ایفا می‌کند. این فریمورک به طور گسترده از تزریق وابستگی برای مدیریت اجزای مختلف خود مانند کنترلرها، سرویس‌ها، میدلورها و غیره استفاده می‌کند. استفاده از کانتینر تزریق وابستگی داخلی در ASP.NET Core نه تنها توسعه را تسهیل می‌کند بلکه به ایجاد برنامه‌های ماژولار، قابل تست و نگهداری کمک می‌کند.

 

نتیجه‌گیری
تزریق وابستگی یک الگوی طراحی قدرتمند است که با جدا کردن مسئولیت ایجاد وابستگی‌ها از خود اشیاء، انعطاف‌پذیری، قابلیت تست، قابلیت نگهداری و قابلیت گسترش نرم‌افزارها را به طور چشمگیری افزایش می‌دهد. در دات نت، این الگو از طریق تزریق سازنده، ویژگی و متد قابل پیاده‌سازی است و کانتینرهای تزریق وابستگی مانند کانتینر داخلی ASP.NET Core و کانتینرهای شخص ثالث، مدیریت خودکار وابستگی‌ها را تسهیل می‌کنند. درک و استفاده صحیح از تزریق وابستگی، یک مهارت ضروری برای هر توسعه‌دهنده دات نت است که به دنبال ساخت نرم‌افزارهای با کیفیت و پایدار است. با پذیرش این الگو، می‌توان گامی بزرگ در جهت ایجاد سیستم‌های نرم‌افزاری مدرن و قابل انطباق برداشت.

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

0 نظر

    هنوز نظری برای این مقاله ثبت نشده است.