آموزش الگوی طراحی تزریق وابستگی (Dependency Injection) در برنامه نویسی

آموزش الگوی طراحی تزریق وابستگی (Dependency Injection) در برنامه نویسی

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

الگوی طراحی تزریق وابستگی (Dependency Injection)

در قسمت های قبلی از این آموزش ما توانستیم با استفاده از تکنیک های متنوع و متعددی کلاس های خود را به حالت Loosely Coupled در بیاوریم. اما از این به بعد در رابطه با الگوهای طراحی Dependency Injection و Strategy صحبت خواهیم کرد تا بتوانیم وظیفه ساخت Dependency را به طور کامل از کلاسی که وابستگی دارد خارج کنیم. این سومین قدم ما در ساختن کلاس هایی است که کاملاً Loosely Coupled هستند.

تزریق وابستگی یا Dependency Injection یک الگوی طراحی است که با استفاده از آن میتوانیم Inversion of Control را پیاده سازی کنیم. به عبارت دیگر می توانیم وظیفه ساختن یک شی از Dependency را در بیرون از کلاسی که به آن Dependency نیاز دارد منتقل کنیم. کلاسی که Dependency را می‌سازد می‌تواند به روش های مختلف شی ساخته شده در اختیار کلاس وابسته قرار دهد. در قالب یک جمله با استفاده از Dependency Injection ما وظیفه ساختن اشیایی که به آنها وابستگی داریم را بیرون از کلاس وابسته انجام می دهیم.

در الگوی طراحی Dependency Injection به طور کلی سه نوع کلاس وجود دارند:

  • کلاس Client: این کلاس در واقع کلاسی وابسته است. یا به عبارتی کلاسی است که برای انجام وظیفه ی خود به یک کلاس دیگر تحت عنوان Service نیازمند است.
  • کلاس Service: کلاس Service کلاسی است که کلاس Client’t برای انجام کار خود به آن نیازمند است. این کلاس Dependency ما است.
  • کلاس Injector: این کلاس وظیفه تزریق کردن یک شی مناسب از کلاس Service را به درون کلاس Client را دارد.

در تصویر زیر ارتباط بین این سه نوع کلاس در الگوی طراحی Dependency Injection دیده می شود.

DI - آموزش الگوی طراحی تزریق وابستگی (Dependency Injection) در برنامه نویسی

همانطور که در تصویر بالا مشاهده می کنید کلاس Injector یک شی از کلاس Service را ساخته و آن را به درون کلاس Client تزریق می‌کنند. با استفاده از این الگوی طراحی وظیفه ساختن یک شی جدید از کلاس Service در درون کلاس Client انجام نمی شود و این وظیفه به کلاس Injector داده می شود.

انواع روش های تزری وابستگی (Dependency Injection)

همانطور که در قسمت قبل گفتیم کلاس Injector یک شی از کلاس Service یا همان Dependency را به درون کلاس Client یا همان کلاس وابسته تزریق میکند. کلاس Injector این Dependency ها را به طور کلی به سه روش در درون کلاس Client تزریق می کند: با استفاده از تابع سازنده و یا با استفاده از یک پروپرتی و یا با استفاده از یک متد. در ادامه یک توصیف کوتاه در رابطه با هر کدام از این سه روش خدمت شما ارائه خواهد شد.

تزریق از طریق تابع سازنده (Constructor Injection): در این روش کلاس Injector یک شی از کلاس Service یا همان Dependency را از طریق تابع سازنده کلاس Client به آن تحویل می‌دهد.

تزریق از طریق پروپرتی (Property Injection): در این روش که همچنین با نام Setter Injection نیز از آن یاد می‌شود Dependency کلاس Client از طریق یک پروپرتی که در درون کلاس Client تعریف شده است به آن تحویل داده می شود.

تزریق از طریق متد (Method Injection): در این روش کلاس Client یک اینترفیس را پیاده سازی می کند که این اینترفیس حاوی متدی برای فراهم کردن Dependency مربوطه است. همچنین کلاس Injector از این اینترفیس برای تحویل دادن Dependency مورد نیاز کلاس Client استفاده می ‌کند.

اگر موافق باشید در ادامه به مثال قسمت های قبلی از این آموزش نگاهی بیندازیم. مثال مربوط به یک مشتری و کلاس DataAccess و همچنین کلاس Factory. در قسمت قبلی کد های مربوط به کلاس CustomerBusinessLogic برای به دست آوردن یک شی از کلاس CustomerDataAcces به شکل زیر نوشته شدند.

public interface ICustomerDataAccess
{
    string GetCustomerName(int id);
}

public class CustomerDataAccess: ICustomerDataAccess
{
    public CustomerDataAccess() {
    }

    public string GetCustomerName(int id) {
        return "Dummy Customer Name";        
    }
}

public class DataAccessFactory
{
    public static ICustomerDataAccess GetCustomerDataAccessObj() 
    {
        return new CustomerDataAccess();
    }
}

public class CustomerBusinessLogic
{
    ICustomerDataAccess _custDataAccess;

    public CustomerBusinessLogic()
    {
        _custDataAccess = DataAccessFactory.GetCustomerDataAccessObj();
    }

    public string GetCustomerName(int id)
    {
        return _custDataAccess.GetCustomerName(id);
    }
}

مشکلی که این روش دارد این است که در درون کلاس CustomerBusinessLogic ما از کلاس DataAccessFactory استفاده می کنیم. بنابراین اگر در آینده لازم باشد که یک پیاده سازی جدید از اینترفیس ICustomerDataAcces را داشته باشیم و از آن در درون کلاس CustomerBusinessLogic استفاده کنیم نیازمند ایجاد تغییرات در درون کلاس CustomerBusinessLogic هستیم. الگوی طراحی Dependency Injection این مشکل را به وسیله تزریق کردن Dependency ها حل و فصل می کند.

در تصویر زیر نموداری از نحوه پیاده سازی Dependency Injection در این مثال را می بینید.

DI example - آموزش الگوی طراحی تزریق وابستگی (Dependency Injection) در برنامه نویسیمانطور که در تصویر مشخص است کلاس CustomerService به عنوان کلاس Injector نقش ایفا می کند و یک شی از کلاس Service که همان CustomerDataAcces هست را به کلاس Client یعنی CustomerBusinessLogic تحویل می‌دهد. این تزریق می تواند از یکی از سه روشی که در قسمت قبل از آن صحبت کردیم انجام شود. این روش ما را به سمت یک طراحی Loosely Coupled سوق می دهد. بیاید با هم این مثال را به طور دقیق ‌تر بررسی کنیم.

تزریق وابستگی از طریق تابع سازنده (Constructor Injection)

همانطور که در قسمت قبل گفتیم ما می‌توانیم Dependency یک کلاس را در زمان ساخته شدن یک شی از آن در درون تابع سازنده ش تزریق کنیم. لطفاً نگاهی به مثال زیر بیندازید که با استفاده از روش تزریق از طریق تابع سازنده وابستگی مربوط به CustomerBusinessLogic که از نوع CustomerDataAcces هست به آن تحویل داده می شود.

public class CustomerBusinessLogic
{
    ICustomerDataAccess _dataAccess;

    public CustomerBusinessLogic(ICustomerDataAccess custDataAccess)
    {
        _dataAccess = custDataAccess;
    }

    public CustomerBusinessLogic()
    {
        _dataAccess = new CustomerDataAccess();
    }

    public string ProcessCustomerData(int id)
    {
        return _dataAccess.GetCustomerName(id);
    }
}

public interface ICustomerDataAccess
{
    string GetCustomerData(int id);
}

public class CustomerDataAccess: ICustomerDataAccess
{
    public CustomerDataAccess()
    {
    }

    public string GetCustomerName(int id) 
    {
        //get the customer name from the db in real application        
        return "Dummy Customer Name"; 
    }
}

در این کد کلاس CustomerBusinessLogic حاوی یک تابع سازنده است که یک پارامتر از نوع ICustomerDataAcces را دریافت می‌کند. هم اکنون زمانی که یک شی از CustomerBusinessLogic ساخته می شود باید Dependency آن یعنی یک کلاس که اینترفیس ICustomerDataAcces را پیاده سازی کرده است از طریق تابع سازنده به درون آن تزریق شود.

public class CustomerService
{
    CustomerBusinessLogic _customerBL;

    public CustomerService()
    {
        _customerBL = new CustomerBusinessLogic(new CustomerDataAccess());
    }

    public string GetCustomerName(int id) {
        return _customerBL.GetCustomerName(id);
    }
}

همانطور که در کد بالا مشاهده می کنید کلاس CustomerService یک شی از کلاس CustomerDataAcces را ساخته و آن را به درون تابع سازنده کلاس CustomerBusinessLogic تزریق می کند. با استفاده از این روش دیگر کلاس CustomerBusinessLogic نیازی به ساختن یک شی از کلاس CustomerDataAcces با استفاده از کلمه کلیدی new و یا حتی استفاده از کلاس DataAccessFactory ندارد. بلکه کلاس CustomerService یک شی از کلاس ICustomerDataAcces را ساخته و در زمان ساخته شدن یک شی از کلاس CustomerBusinessLogic این شی را به درون تابع سازنده آن تزریق می کند. با استفاده از این روش دو کلاس CustomerBusinessLogic و CustomerDataAcces به طور کامل Loosely Coupled در می آیند.

تزریق وابستگی از طریق پروپرتی (Property Injection)

در این روش Dependency از طریق یک Property به درون کلاس تزریق می شود. لطفاً کد زیر را در نظر بگیرید.

public class CustomerBusinessLogic
{
    public CustomerBusinessLogic()
    {
    }

    public string GetCustomerName(int id)
    {
        return DataAccess.GetCustomerName(id);
    }

    public ICustomerDataAccess DataAccess { get; set; }
}

public class CustomerService
{
    CustomerBusinessLogic _customerBL;

    public CustomerService()
    {
        _customerBL = new CustomerBusinessLogic();
        _customerBL.DataAccess = new CustomerDataAccess();
    }

    public string GetCustomerName(int id) {
        return _customerBL.GetCustomerName(id);
    }
}

همانطور که در کد بالا مشاهده می کنید کلاس CustomerBusinessLogic حاوی یک پروپرتی به نام DataAccess است که در درون آن میتوانید یک شی از نوع ICustomerDataAcces را قرار دهید. بنابراین CustomerService وظیفه ساختن یک شی از این اینترفیس برای مثال CustomerDataAcces را دارد و سپس این شی را در درون پروپرتی DataAccess از کلاس CustomerBusinessLogic قرار می ‌دهد.

تزریق وابستگی از طریق متد (Method Injection)

در این روش Dependency ها از طریق تعدادی متد در درون کلاس وابسته تزریق می‌ شوند. این متد می‌تواند متدی از خود کلاس وابسته یا متدی از درون یک اینترفیس باشد. کد زیر نحوه انجام این کار را به نمایش می گذارد.

interface IDataAccessDependency
{
    void SetDependency(ICustomerDataAccess customerDataAccess);
}

public class CustomerBusinessLogic : IDataAccessDependency
{
    ICustomerDataAccess _dataAccess;

    public CustomerBusinessLogic()
    {
    }

    public string GetCustomerName(int id)
    {
        return _dataAccess.GetCustomerName(id);
    }
        
    public void SetDependency(ICustomerDataAccess customerDataAccess)
    {
        _dataAccess = customerDataAccess;
    }
}

public class CustomerService
{
    CustomerBusinessLogic _customerBL;

    public CustomerService()
    {
        _customerBL = new CustomerBusinessLogic();
        ((IDataAccessDependency)_customerBL).SetDependency(new CustomerDataAccess());
    }

    public string GetCustomerName(int id) {
        return _customerBL.GetCustomerName(id);
    }
}

در کد بالا کلاس CustomerBusinessLogic یک اینترفیس به نام IDataAccessDependency را پیاده سازی می کند. این اینترفیس حاوی یک متد به اسم DepEndency است. کلاس Injector یعنی همان CustomerService از این متد و این اینترفیس برای تزریق کردن یک شی از ICustomerDataAcces به درون کلاس CustomerBusinessLogic استفاده می‌کند.

خب تا اینجا ما توانستیم با استفاده از دو الگوی طراحی Dependency Injection و Strategy به یک طراحی شی گرای Loosely Coupled برسیم. در پروژه های واقعی اغلب این دو الگوی طراحی به صورت دستی پیاده سازی نمی شوند چرا که پیاده سازی دستی آنها بسیار زمان ‌بر است. از همین جهت از فریم ورک هایی تحت عنوان IoC Container استفاده می ‌شود.

در پایان قصد داریم از شما تشکر کنیم که از ابتدا تا پایان این آموزش با وب سایت همراه بودید. امیدواریم که این آموزش شبیه دیگر آموزش های وب سایت پرووید مورد توجه شما قرار گرفته باشد.

مرتضی گیتی
بدون نظر

ارسال نظر

نظر
نام
ایمیل
وب سایت