با وابستگی‌ها دوست باشیم

با وابستگی‌ها دوست باشیم

نوشته شده توسط: محمد لطفی
تاریخ انتشار: ۲۶ بهمن ۱۳۹۸
آخرین بروزرسانی: ۱۷ مهر ۱۴۰۱
زمان مطالعه: 20 دقیقه
۰
(۰)

مقدمه

وابستگی یکی از مهمترین مفاهیمی است که برنامه نویسان در حرفه‌ی خود با آن سروکار دارند.این مفهوم همانگونه که از نام آن مشخص است زمانی معنا پیدا می‌کند که یک ماژول، شی، و یا کلاس برای اجرای صحیح عملکرد خود نیازمند کلاس یا ماژول‌های دیگر باشد. به عنوان مثال، بدن ما برای ادامه حیات به مواد غذایی نیازمند است. پس بین بدن ما و مواد غذایی وابستگی وجود دارد.وجود وابستگی همیشه اجباری نیست ولی وقتی با شرایطی مواجه می‌شویم که در آن اشیایی طراحی کرده‌ایم که نیاز دارند به هم وابسته باشند، باید در مورد مدیریت این وابستگی‌ها فکر کنیم. نحوه پیاده سازی این وابستگی‌ها در پروژه‌ها و ماژول‌های ما می‌تواند روی قابلیت‌های تست پذیری و انعطلاف پذیری سیستم‌های ما تاثیر بسزایی داشته باشد. همچنین به ما کمک می‌کند تا برنامه خود را به گونه‌ای توسعه دهیم که بعدا نگهداری آن راحت‌تر باشد.
در ادامه با همدیگر در مورد اصول، الگوها، روش‌ها و ابزارهایی صحبت می‌کنیم که به ما کمک می‌کنند مسئله وابستگی را در کارهای خود بهتر درک کرده و آنها را مدیریت کنیم. و با هم بررسی می‌کنیم هنگامی که دو ماژول (کلاس و …) با هم ارتباط دارند، این ارتباط کجا؟ کی؟ چگونه؟ و با چه ابزاری پیاده سازی شود.

اصل معکوس سازی وابستگی‌ها

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

  • منظور از ماژول سطح بالا یا سطح پایین چیست؟
  • در شرایطی باید وابستگی وجود داشته باشد، منظور از وابسته نبودن در عبارت بالا چیست؟
  • چرا اصلا ماژول‌های سطح بالا و ماژول‌های سطح پایین باید به قرارداد وابسته باشند؟

این اصل دو بند دارد

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

پس تا اینجا می‌فهمیم که وابستگی بین این ماژول‌ها باید از چه جنسی باشد.باید از جنس قراردادها باشد. چرا قرارداد؟ وقتی یک کلاس به یک قرارداد وابسته می‌شود با تغییر قرارداد کلاس نیز باید تغییر کند برای همین هم ماژول‌های سطح بالا تغییر می‌کنند و هم سطح پایین ولی با تغییر هر یک از ماژول‌های سطح بالا یا سطح پایین تغییری در قرارداد رخ نمی‌دهد، بنابراین تغییرات درون ماژول‌ها روی عملکرد سایر ماژول‌ها تاثیر نمی‌گذارد.
برای ماژول سطح بالا یک بخش از نرم افزار خود را در نظر بگیرید. مثلا بخش تبلیغات. این واحد نیاز دارد تا به یکسری از کاربران شما ایمیل ارسال کند. برای همین باید عملیات ارسال ایمیل در سیستم شما وجود داشته باشد. این عملیات می‌تواند به عنوان بخشی از خود ماژول تبلیغات باشد

public class Advertise
{
    public void SendEmial()
    {
        //Do ...
    }
}

با اینکار نیاز بخش تبلیغات به ارسال ایمیل را بر طرف ساختیم. اما اگر بخشی دیگر بخواهد ایمیل ارسال کند چه اتفاقی می‌افتد . یا باید تن به تکرار بدهیم و دوباره این بخش را پیاده کنیم و یا اینکه کلا بخش ارسال ایمیل را از دل ماژول تبلیغات بیرون بکشیم. و به عنوان یک ماژول دیگر آن را داشته باشیم. ماژول تبلیغات می‌تواند مثالی از یک ماژول سطح بالا و ماژول ارسال ایمیل یک ماژول سطح پایین می‌باشد. که در سایر بخش‌های سیستم نیز می‌تواند استفاده شود.

public class Advertise
{
    public void SendEmial()
    {
        EmialSender sender =new EmialSender();
        sender.Send();
    }
}
public class EmialSender
{
    public void Send()
    {
        //Do ...
    }
}

بر اساس اصل DIP سیستم تبلیغات نباید به پیاده سازی سیستم ارسال ایمیل وابسته باشد. چرا؟ کار سیستم ارسال ایمیل فرستادن پیام است اگر در آینده نحوه فرستادن پیام عوض شود و تصمیم بگیریم از سرویس دهنده ایمیل دیگری استفاده کنیم یا کلا به جای ارسال ایمیل از پیام کوتاه یا SMS استفاده کنیم چکار باید کرد. تغییر یک بخش کوچک روی سایر بخش‌های سیسیتم تاثیر می‌گذارد فقط به خاطر اینکه وابستگی به نحوه ارسال پیام یعنی ایمیل کردن ایجاد شده بود. یا اگر حین ارسال ایمیل مشکلی پیش بیاید ممکن است روی اجزای دیگر تاثیر بگذارد.
حال می‌توان به جای آن یک قرارداد (Interface) بین ماژول تبلیغات و ماژول ارسال ایمیل تعریف کرد، به نام ارسال پیام که در آن مشخص شده باشد ارسال پیام به چه صورتی باشد. و هر دوی این ماژول‌ها به این قرارداد وابسته باشند. از این به بعد هر ماژول دیگری که این قرارداد را رعایت کند می‌تواند نیاز ماژول تبلیغات برای ارسال پیام برطرف کند.

public interface IMessageSender
{
    void Send();
}
public class EmialSender:IMessageSender
{
    public void Send()
    {
        //Do ...
    }
}
public class SMSSender:IMessageSender
{
    public void Send()
    {
        //Do ...
    }
}
public class Advertise
{
    private readonly IMessageSender _sender;
    public Advertise(IMessageSender sender)
    {
        _sender = sender;
    }
    public void SendMessage()
    {
        _sender.Send();
    }
}

الگوی معکوس سازی کنترل (Inversion of Control)

بعد از اصل DIP این سوال به وجود می‌آید که چگونه باید این اصل را رعایت کرد و یا اینکه چگونه مطمئن شویم آن را رعایت کرده‌ایم. الگوی معکوس سازی کنترل IOC (Inversion of Control) یکی از راه‌های پیاده سازی این اصل است بر اساس این الگو به جای اینکه شما وابستگی‌ها را درون خود ماژول پیاده کنید بهتر است که کنترل آن را به یک بخش بیرونی بسپارید و بر اساس اصل DIP فقط با قرارداد به آن ارتباط داشته باشیدبه عنوان مثال بدن انسان به مواد غذایی نیاز دارد به جای اینکه خود آن را بسازد از طریق تغذیه آن را تامین می‌کند. دقت کنید که بدن دیگر به ماده خاصی وابسته نیست بلکه هر ماده‌ای که به عنوان غذا شناخته شود می‌تواند نیاز بدن را تامین کند.
در مثال قبل اگر کلاس Advertise به قرار داد IMessageSender وابسته شد و دیگر درگیر نحوه ارسال پیام نمی‌باشد. این کار را به بخش های SmsSender و EmailSender واگذار می‌کند.

تزریق وابستگی‌ها (Dependency Injection)

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

  • تزریق به کمک سازنده کلاس
  • تزریق به کمک خصیصه‌های عمومی کلاس (property)
  • تزریق به توابع درون کلاس

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

تزریق به کمک سازنده (Constructor Injection)

در روش Constructor Injection هنگامی که نمونه از یک کلاس ساخته می‌شود در پارامترهای ورودی سازنده کلاس وابستگی مورد نظر را دریافت می‌کنیم. با این کار هر کلاسی که می‌خواهد از این کلاس استفاده کند و از آن شی بسازد باید قبل از آن وابستگی‌های آن را تامین کند. دقت داشته باشید که این روش زمانی به کار می‌آید که سازنده کلاس در دسترس باشد و قابلیت نمونه گیری از کلاس وجود داشته باشد. در غیر اینصورت نمیتوان از این روش استفاده کرد.
در مثال کلاس تبلیغات دیدیم که وابستگی IMessageSender چگونه از طریق سازنده کلاس تزریق شد.

public class Advertise
{
    private readonly IMessageSender _messageSender;
    public Advertise(IMessageSender messageSender)
    {
        _ messageSender = messageSender;
    }
    public void SendMessage()
    {
        _ messageSender.Send();
    }
}

تزریق به کمک خصیصه‌های عمومی کلاس (Setter Injection)

در این روش که به آن Property Injection هم می‌گویند، دیگر از سازنده کلاس استفاده نمی‌کنیم پس هنگامی که از کلاس نمونه می‌سازیم هیچ الزامی به تامین کردن نیازمندی‌های کلاس وجود ندارد. یکی از نقاط مثبت این روش این است که وابستگی‌ها را زمانی فقط تامین می‌کنیم که به آنها نیاز داشته باشیم. یکی از معایب این روش پیچیده شدن عملیات استفاده از کلاس است . چون وقتی درون کلاس داریم با وابستگی‌ها کار می‌کنیم از این که واقعا آنها از بیرون مقدار دهی شده باشند مطمئن نیستیم و در کدها خود باید این شرط چک کردن را قبل از استفاده از وابستگی‌ها بنویسیم.

public class Advertise
{
    public IMessageSender MessageSender;
    public void SendMessage ()
    {
        MessageSender.Send();
    }
}

تزریق به توابع درون کلاس (Method Injection)

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

public class Advertise
{
public void SendMessage(IMessageSender MessageSender)
{
MessageSender.Send();
}

ابزارهای تزریق وابستگی (Dependency Injection Container)

تامین وابستگی‌‌های یک کلاس می‌تواند بسیار ساده و یا بسیار پیچیده باشد. این سناریو را در نظر بگیرید که کلاس شما به یک قرارداد یا سرویس دیگری وابسته است و خود این سرویس نیز درون خود به ماژول‌ها و ابزارهای مختلفی وابسته باشد. به این ترتیب عملیات ساخت کلاس‌ها و تامین نیازمندی‌ها می‌تواند سخت‌تر و پیچیده‌تر از چیزی باشد که به نظر می‌آید. در این شرایط بهترین کار استفاده از ابزارهای تخصصی تولید شده برای این کار است. در ابزارها تمامی وابستگی‌ها را بر اساس تنظیماتی که برای آنها تعریف می‌کنید. به صورت اتوماتیک شناسایی شده و تامین می‌شوند.

این سوال پیش می‌آید که از کدام ابزار استفاده کنیم؟

ابزارهای مختلفی برای استفاده وجود دارند. برای انتخاب درست باید ابتدا به نوع کاری که انجام می‌دهید دقت کنید، هر یک از این ابزارها معایب و مزایای خود را دارند. ممکن است یک ابزار بسیار پیچیده باشد ولی سرعت و عملکرد بسیار بالایی داشته باشد. یا اینکه یک ابزار مستندات بهتری نسبت به بقیه داشته باشد. پس لازم است که قبل از استفاده به دقت ابزار مورد نظر را بررسی کنید.
برای نمونه می‌توان به موارد زیر اشاره کرد.

  • Autofac
  • Castle Windsor
  • Lamar
  • LightInject
  • Ninject
  • SimpleInjector
  • Spring.NET
  • Unity

چه رتبه ای می‌دهید؟

میانگین ۰ / ۵. از مجموع ۰

اولین نفر باش

title sign
دانلود مقاله
با وابستگی‌ها دوست باشیم
فرمت PDF
6 صفحه
حجم 1 مگابایت
دانلود مقاله
title sign
معرفی نویسنده
محمد لطفی
مقالات
3 مقاله توسط این نویسنده
محصولات
0 دوره توسط این نویسنده
محمد لطفی
title sign
دیدگاه کاربران

    • سلام.
      از ارائه این مقاله ارزشمند ممنون هستم.
      یک چیزی که در طراحی کلاس ها باید دقت کنیم که معمولا نمی کنیم، اینکه ارتباطات بین کلاس ها را همیشه اصل معکوس سازی باید وابستگی ها را در نظر بگیریم ولی متاسافانه این کار انجام نمی دهیم، از نظر تئوری خوب میفهمیم ولی عملا انجام نمی دهیم. کاش روی این موضوع هم فوکوس کنید، شاید با گفتن مثال های بیشتر بتونیم فهم کاملی از این اصل داشته باشیم.
      لطفا در یک مقاله دیگر از ابزارهای گفته شده، مثالی بزنید.
      خود دات نت کور هم به صورت داخلی از DI پشتیبانی می کنه؟
      ممنون از وقتی که گذاشتید.

      • مرسی از لطف شما
        بله همونطور که میفرمایید این یک اصل و باید رعایت بشه. تا حدودی سعی کردیم در مقاله مثال کافی بیاوریم.
        برنامه داریم که مقاله اموزش کار با این ابزار ها را تهیه کنیم و در پایان باید عرض کنم دات نت کور به صورت داخلی از DI پشتیبانی میکند و در کنار آن نیز میتوانید بنابر نیازتون از سایر ابزرا ها هم استفاده کنید.

    • سلام.
      از ارائه این مقاله ارزشمند ممنون هستم.
      یک چیزی که در طراحی کلاس ها باید دقت کنیم که معمولا نمی کنیم، اینکه ارتباطات بین کلاس ها را همیشه اصل معکوس سازی باید وابستگی ها را در نظر بگیریم ولی متاسافانه این کار انجام نمی دهیم، از نظر تئوری خوب میفهمیم ولی عملا انجام نمی دهیم. کاش روی این موضوع هم فوکوس کنید، شاید با گفتن مثال های بیشتر بتونیم فهم کاملی از این اصل داشته باشیم.
      لطفا در یک مقاله دیگر از ابزارهای گفته شده، مثالی بزنید.
      خود دات نت کور هم به صورت داخلی از DI پشتیبانی می کنه؟

      ممنون از وقتی که گذاشتید.