اصل معکوس سازی وابستگی (Dependency Inversion Principle) در برنامه نویسی

پرووید

دسته های مقالات

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

اصل معکوس سازی وابستگی (Dependency Inversion Principle)

در قسمت‌ های قبلی از این آموزش از وب سایت پرووید ما در رابطه با
پیاده سازی اصل Inversion of Control با استفاده از الگوی طراحی Factory صحبت کردیم. این موضوع اولین قدم ما در رسیدن به
یک طراحی Loosely Coupled بود. در ادامه این آموزش ما در رابطه با پیاده سازی اصل Dependency Inversion به عنوان دومین
قدم در روند رسیدن به یک طراحی Loosely Coupled صحبت خواهیم کرد.

در ابتدا باید با هم اصل Dependency Inversion (معکوس سازی
وابستگی) را بررسی کنیم. این اصل یکی از اصول پنجگانه SOLID است که در طراحی شی گرا اولین بار توسط آقای Robert C.
Martin مطرح شد.

تعریف اصل Dependency Inversion

بند اول: ماژول های سطح بالا (High Level Module) نباید به
ماژول های سطح پایین (Low Level Module) وابسته باشند. بلکه هر دوی آنها باید به Abstraction وابسته باشند.

بند دوم: Abstraction ها نباید وابسته به Details (جزئیات)
باشند. Details (جزئیات) باید وابسته به Abstraction باشند. بیایید برای بهتر فهمیدن اصل Dependency Inversion مثالی
که از قسمت های قبلی بررسی کرده بودیم را یک بار دیگر در نظر بگیریم. لطفاً کد زیر را با دقت نگاه کنید.

 
public class CustomerBusinessLogic
{
    public CustomerBusinessLogic()
    {
    }

    public string GetCustomerName(int id)
    {
        DataAccess _dataAccess = DataAccessFactory.GetDataAccessObj();

        return _dataAccess.GetCustomerName(id);
    }
}

public class DataAccessFactory
{
    public static DataAccess GetDataAccessObj() 
    {
        return new DataAccess();
    }
}

public class DataAccess
{
    public DataAccess()
    {
    }

    public string GetCustomerName(int id) {
        return "Dummy Customer Name"; // get it from DB in real app
    }
}

در مثال بالا ما الگوی طراحی Factory را برای رسیدن به
Inversion of Control استفاده کردیم. اما موضوع اینجاست که کلاس CustomerBusinessLogic از یک کلاس Concrete
(امیدوارم تفاوت بین کلاس های Concrete و Abstract را بدانید.) استفاده می کند و آن DataAccess است. بنابراین بین دو
کلاس DataAccess و CustomerBusinessLogic هنوز هم Tight Coupling وجود دارد. هر چند که ما مسئولیت ساختن یک شی جدید
از این وابستگی را به کلاس Factory محول کردیم. بیایید در ادامه با پیاده‌سازی اصل Dependency Inversion بر روی دو
کلاس DataAccess و CustomerBusinessLogic آن ها را بیش از پیش Loosely Coupled کنیم.

در بند اول از تعریف اصل Dependency Inversion گفتیم که ماژول های سطح بالا نباید به
ماژول های سطح پایین وابسته باشند. بلکه هر دوی آنها باید به Abstraction وابسته باشند. در اینجا بیایید تصمیم
بگیریم که کدام کلاس ماژول سطح بالا و کدام کلاس ماژول سطح پایین است. ممکن است حدس زده باشید که ماژول سطح بالا
کلاس یا ماژولی است که به بقیه ی کلاس ها یا ماژول ها وابستگی دارد. بنابراین در مثال ما CustomerBusinessLogic که
به کلاس DataAccess وابسته است ماژول سطح بالا و DataAccess ماژول سطح پایین است. بنابراین اگر بخواهیم بند اول از
اصل Dependency Inversion را پیاده سازی کنیم نباید کلاس CustomerBusinessLogic به کلاس DataAccess وابسته باشد. هر
دوی آنها باید به Abstraction وابسته باشند. بند دوم در تعریف اصل Dependency Inversion می گفت که Abstraction ها
نباید وابسته به جزئیات باشند. بلکه جزئیات باید وابسته به Abstraction باشند.

انتزاع یا Abstraction چیست؟

انتزاع یا Abstraction و کپسولهای سازی یا Encapsulation
دو تا از مهمترین اصول در برنامه نویسی شی گرا هستند. تعریف های زیادی توسط افراد مختلف برای این دو اصل در نظر
گرفته شده است. اما بیایید در قسمت بعدی در قالب یک مثال معنی Abstraction را درک کنیم.

دو مفهوم Abstract و Concrete در طراحی شی گرا اغلب متضاد
هم هستند. در زبان انگلیسی چیزی که Abstract باشد غیر ملموس یا Non-concrete است. از نقطه نظر برنامه نویسی دو
کلاس CustomerBusinessLogic و DataAccess کلاس های Concrete هستند. به این معنا که ما می‌ توانیم از آنها اشیایی
بسازیم. بنابراین Abstraction در زبان های برنامه نویسی به معنی ساختن یک Interface یا کلاس Abstract ایست که
Non-concrete باشد یا به عبارتی نتوانیم از آن یک شی بسازیم.

همانطور که ممکن است بدانید در زبان های برنامه نویسی شی
گرا از قبیل سی شارپ نمی‌توان یک شی از یک Interface یا یک کلاس Abstract ساخت. اگر بخواهیم این مفاهیم را در
کنار قواعد مربوط به Dependency Inversion بگوییم کلاس CustomerBusinessLogic که یک ماژول سطح بالا است نباید به
کلاس DataAccess که یک کلاس Concrete و البته ماژول سطح پایین است وابسته باشد. بلکه هر دوی این کلاس ها باید
وابسته به Abstraction باشند یا به عبارتی هر دوی این کلاس ها باید وابسته به Interface یا کلاس Abstract باشند.

سوالی که ممکن است در این قسمت مطرح شود این است که در Interface یا کلاس Abstract
ی خواهیم ساخت چه چیزی را باید قرار دهیم؟ همان طور که ممکن است یادتان باشد دیدید که کلاس
CustomerBusinessLogic از مدتی به نام GetCustomerName که در کلاس DataAccess تعریف شده بود استفاده می‌کرد. دقت
کنید که در دنیای واقعی ممکن است متد های بیشتری در رابطه با کار کردن با داده ‌های Customer در کلاس DataAccess
تعریف شود. اما برای مثال ما همین یک متد کفایت می کند. در واقع ما باید همین متد را در Interface مان قرار
دهیم.

همان طور که در کد زیر می‌بینید Interface ی با نام ICustomerDataAcces تعریف شده
است و در آن متد GetCustomerName قرار داده شده است که یک پارامتر ورودی از نوع Interface دریافت می کند.

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

در ادامه کلاس CustomerDataAcces را داریم که اینترفیس ICustomerDataAcces را پیاده
سازی می کند.

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

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

لطفا دقت کنید که ما نام کلاس را از DataAccess به CustomerDataAcces تغییر داده
ایم. این موضوع می تواند به قابلیت Readability (قابلیت خوانایی) برنامه کمک کند. پس از آن ما نیاز داریم کلاس
DataAccessFactory را طوری تغییر دهیم که به جای برگرداندن یک شی از کلاس DataAccess یک شی از اینترفیس
ICustomerDataAcces را برگرداند.

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

در ادامه کلاس CustomerBusinessLogic را می بینیم که به جای استفاده کردن از کلاس
DataAccess از اینترفیس ICustomerDataAcces استفاده می‌ کند.

 
public class CustomerBusinessLogic
{
    ICustomerDataAccess _custDataAccess;

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

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

تا اینجای کار ما توانسته ایم که اصل Dependency Inversion را در مثال خود پیاده
سازی کنیم. هم اکنون ماژول سطح بالای ما یعنی کلاس CustomerBusinessLogic به ماژول سطح پایین یعنی
CustomerDataAccess وابسته نیست. بلکه هر دوی آنها به یک Abstraction که در قالب یک اینترفیس با نام
ICustomerDataAcces پیاده سازی شده است وابسته هستند.

علاوه بر این اینترفیس ICustomerDataAcces که در نقش یک Abstraction قرار گرفته است
بند دوم از تعریف اصل Dependency Inversion را بر قرار می کند. به عبارت دیگر Abstraction یعنی همان اینترفیس
ICustomerDataAcces به جزئیات یعنی کلاس CustomerDataAcces وابسته نیست. بلکه جزئیات یعنی کلاس
CustomerDataAcces به Abstraction یعنی اینترفیس ICustomerDataAcces وابسته است. منظور از جزئیات در این قسمت
جزئیات پیاده سازی یا همان Implementation Details می باشد. در ادامه کد کامل مربوط به مثال اصل Dependency
Inversion را می بینید.

 
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);
    }
}

ممکن است فهمیده باشید که مهمترین مزیت پیاده سازی اصل Dependency Inversion در
مثال بالا این است که کلاس های CustomerBusinessLogic و CustomerDataAcces به صورت Loosely Coupled طراحی شده
اند. چرا که CustomerBusinessLogic وابسته به کلاس DataAccess که یک کلاس Concrete است نمی باشد. بلکه وابستگی
کلاس CustomerBusinessLogic این بار به Abstraction یا Interface ی به نام ICustomerDataAcces است.

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

اگر خاطرتان باشد در ابتدای این آموزش عرض کردیم که یکی از مشکلات کلاس هایی که
Tight Coupling دارند این است که اگر بخواهیم اطلاعات مربوط به یک مشتری را از یک منبع داده ای دیگر مثل یک وب
سرویس بخوانیم با مشکلاتی روبرو خواهیم شد. اما با این روش خیلی راحت می‌توانیم هر DataAccess دیگری را به شرط
اینکه اینترفیس ICustomerDataAcces را پیاده سازی کند در درون CustomerBusinessLogic مورد استفاده قرار دهیم. با
همه تلاشهایی که تا اینجا انجام داده ایم هنوز کلاس های ما به صورت کاملا Loosely Coupled نیستند چرا که کلاس
CustomerBusinessLogic نیازمند استفاده کردن از کلاس Factory برای به دست آوردن یک شی از اینترفیس
ICustomerDataAcces است. در ادامه ما از تکنیک Dependency Injection یا همان تزریق وابستگی که یک الگوی طراحی
اسن استفاده خواهیم کرد. در ادامه خواهیم دید که چگونه الگوهای طراحی Strategy و Dependency Injection می‌توانند
شرایط مثال ما را از چیزی که هم اکنون هست بهتر کنند.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *