بهترین درک از (Dependency Injection) تزریق وابستگی در جاوا

تزریق وابستگی در جاوا
تاریخ بروزرسانی : 13 مرداد 1399 | تعداد بازدید : 6919 | زمان خواندن مقاله : 15 دقیقه
جاوا،

قطعا در طول زندگی با وسایل زیادی سر و کار دارید، از وسایل اولیه زندگی گرفته تا وسایلی که بر اساس نیاز کاری از آنها استفاده می کنید، مانند: کامپیوتر یا لپ تاپ و گوشی.

همان طور که می دانید یک گوشی هوشمند از قطعات زیادی تشکیل شده مانند: صفحه نمایش، پردازنده، حافظه رم، باتری و کلی قطعات دیگر. اتفاقی پیش می آید و صفحه نمایش گوشی شما آسیب میبیند. حالا چه می کنید؟ اولین کاری که انجام میدهید این است که گوشی خود را به یک نمایندگی برده تا صفحه نمایش آن را تغییر دهد. فرض کنید که گوشی شما جوری طراحی شده است که با آسیب دیدن صفحه نمایش نیاز باشد تا یک گوشی جدید تهیه کنید!!!!

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

در پیاده سازی سیستم های نرم افزاری، شما باید به عنوان یک برنامه نویس با چنین دیدی نسبت به پیاده سازی نرم افزار اقدام کنید یعنی کاری کنید که وابستگی ها را از سطح کلاس کاهش داده و در زمان نیاز این وابستگی را ایجاد کنید برای بیان این مساله در برنامه نویسی از واژه Dependency Injection یا تزریق وابستگی استفاده میکنند.

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

آموزش جاوا

dependency injection در جاوا چیست؟

dependency injection یک الگوی طراحی است که هدفش حذف وابستگی های بین دو کلاس با استفاده از کد است. به مثال زیر در جاوا توجه کنید، وقتی Class1 به Class2 وابستگی دارد ما این وابستگی را با تعریف یک فیلد از Class2 در Class1 انجام میدهیم و سپس با استفاده از کلمه کلیدی new یک شئ از Class2 می سازیم:
public class Class1 {
public Class2 class2 = new Class2();
}

بیان دقیق تر از تزریق وابستگی یا Dependency injection:

در واقع تزریق وابستگی یک تکنیک در برنامه نویسی است که باعث می شود کلاس های شما، مستقل از وابسته هایشان عمل کنند. این امر با جدا کردن «استفاده از یک شی» از «ساخت آن شی» حاصل می شود. این تکنیک به برنامه نویسان کمک می کند که بتوانند دو اصل از اصول طراحی شی گرا یعنی Dependency inversion principle یا اصل وابستگی معکوس و Single responsibility principle یا اصل تک مسئولیتی را بتوانند رعایت کنند.
Dependency injection یا تزریق وابستگی معکوس باعث می شود که کلاس های ما کمترین وابستگی را به هم داشته باشند و اصطلاحاً به یک معماری Loosely coupled(اتصال آزادانه) برسیم.
 

در تزریق وابستگی ۴ نقش اصلی وجود دارد:

  1. شی service که مورد استفاده قرار می گیرد.
  2. شی client که از سرویس استفاده می کند.
  3. اینترفیس ها که نحوه استفاده client از service را مشخص می کند.
  4. Injector، وظیفه آن ساخت شی سرویس تا آن را در اختیار Client قرار دهد.

اکنون به بیان تعریف نقش های گفته شده در تزریق وابستگی جاوا میپردازیم:

  • شی service: هر شی که قرار است سرویسی ارائه دهد به عنوان شی service شناخته می شود.
  • شی client: هر شی که از شی service استفاده می کند به عنوان شی client شناخته می شود.
  • Interface: اینترفیس چیزی است که client انتظار دارد service ها در اختیارش بگذارند. اینترفیس توسط سرویس پیاده سازی می شود و فرآیند injection توسط اینترفیس ها انجام می شود. اینترفیس ها کلاس های concrete نیستند یعنی در داخل خود اینترفیس، چیزی پیاده سازی نمی شود بلکه اینترفیس ها در سطح انتزاع یا Abstract می باشند.
  • منظور از انتزاع دید کلی نسبت به یک شیء است مثلا وقتی می‌گوییم میز، چیزی که در ذهن ما نقش می‌بندد یک شکل کلی است ولی وقتی می‌گوییم میز ناهارخوری دقیقا مشخص می‌کنیم که چه نوع میزی است. در نتیجه انتزاع یک دید کلی از یک شیء بحساب می‌آید. در واقع Client نباید نسبت به نحوه پیاده سازی سرویس هایی که از آن استفاده می کند هیچ اطلاعاتی داشته باشد. اینترفیس ها رابط کلاس سرویس و کلاینت هستند.
  •  InjectorInjector وظیفه معرفی سرویس ها به کلاینت را بر عهده داردInjector ها عموماً به صورت پکیج های خارجی در زبان های مختلف برنامه نویسی وجود دارند و لازم نیست برنامه نویس خودش یک injector توسعه دهد.

دلیل استفاده از تزریق وابستگی:

قبل از دلیل استفاده از تزریق وابستگی باید بدانید که یک نوع تزریق وابستگی به نام Dependency injection hard یا وابستگی سخت وجود دارد که باعث بروز مشکلاتی در برنامه میشود هر چه برنامه گسترش پیدا کند این مشکلات نیز بیشتر میشود و هدف استفاده از تزریق وابستگی کاهش این مشکلات است. مشکلاتی که وابستگی سخت یا hard Dependency injection به وجود می آورد عبارت است از:
  • کاهش قابلیت گسترش برنامه
  • کاهش قابلیت نگهداری
  • کاهش قابلیت استفاده مجدد
  • کاهش قابلیت تست
پس ایده اصلی Dependency Injection این می باشد که اتصالات و وابستگی را بصورت جدا از هم ایجاد کرد، بصورتی که کلاس مادر یا شی ایجاد شده به راحتی قابل آزمایش و اجرا باشد. اصولا مشکل زمانی پیش خواهد آمد که ما یک کلاس را ایجاد کرده باشیم و در آینده بخواهیم در یک شی تغییراتی ایجاد کنیم این تغییرات بسیار دشوار می باشد چون به دلیل استفاده نکردن از تزریق وابستگی باید تمام نمونه های آن شی را تغییر دهیم.

روش ها استفاده از تزریق وابستگی در جاوا:

  1. سازنده (Constructor Injection):

در این روش شی‌ای از کلاس سطح پایین به سازنده کلاس سطح بالا ارسال می‌شود که در نهایت نوع آن از جنس Interface است.
  1. متد (Method Injection):

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

مثال از تزریق وابستگی در جاوا:

اکنون که با تزریق وابستگی در جاوا آشنا شدید، میخواهیم این روش را در قالب یک مثال کاربردی برای درک بهتر موضوع برای شما عزیزان بیان کنیم.

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

پس چه کاری انجام دهیم که مجبور به تغییر زمان بر کدها نباشیم؟

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

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

حالا میخواهیم نمونه کدی را با استفاده از تزریق وابستگی در جاوا، مورد بررسی قرار دهیم: کلاسی داریم بنام MessageService که کار ارسال پیام را به چند صورت می خواهد انجام دهد. ما دو کلاس برای ارسال پیام ایجاد کرده ایم که دو نوع پیام را ارسال میکند. بدون استفاده از DI که مخفف Dependency Injection است کدهای ما بصورت زیر خواهد بود:

class TypeA{
 sendMessage(){
---------
---------
}
}
 
class TypeB{
 sendMessage(){
---------
---------
}
}
class SendMessageApplication{
main(){
 send(type);
}

send(int type){
if(type == 1){
TypeA a = new TypeA();
 a.sendMessage();
}else{
TypeB b = new TypeB();
b.sendMessage();
}
}
}

در آینده اگر بخواهیم در کلاس نوع A تغییری ایجاد کنیم برای نمونه نوع C نیز این کار را باید بصورت دستی انجام دهیم، حالا اگر پروژه بسیار بزرگ باشد تصور کنید این کار چقدر دشوار تر خواهد شد.
 

بیشتر بخوانید: آشنایی کامل و جامع با انواع دیزاین پترین در جاوا wink

 

و اما راه حل مساله:
 

ما یک رابط کاربری (interface) ایجاد میکنیم که کار ارسال پیام را انجام دهد و دارای یک متد بنام sendMessage باشد. حال تمامی سرویس دهنده های ما (همان کلاس های ارسال پیام) باید این رابط کاربری را implement کنند. با این کار ما کلاس هایی داریم که همگی از یک رابط کاربری استفاده کرده اند و متدی بنام sendMessage را درون خود دارند که عملیات نهایی ارسال پیام را با توجه به نوع آن برای نمونه کلاس اول متن را بصورت انگلیسی و کلاس دوم بصورت فارسی می فرستد.

حال ما در تابع setter در کلاس MessageApplication از طریق تزریق وابستگی، نوع سرویس مورد نظر( همان کلاس های ارسال پیام) را گرفته و با استفاده از متد messageService.sendMessage(); که اشاره به تابع sendMessage() رابطِ کاربری ما دارد برنامه را اجرا و عملیات ارسال پیام را انجام میدهیم. پس با استفاده از تزریق وابستگی ها کلاس های ما بصورت زیر تغییر خواهند کرد:


interface MessageService{

sendMessage();

}

class TypeA implements MessageService{

void sendMessage(){

 //------------

 //------------

}

}
 

class TypeB implements MessageService{

void sendMessage(){

 //-----------

//-----------

}

}

//----------------------------------------------------

class MessageApplication{

MessageService messageService;

void setMessageService(MessageService service){

this.messageService = service;

}

void sendMessage(){

messageService.sendMessage();

}

}
حال شما می تواند با ایجاد یک شی از کلاس MessageApplication و فراخوانی sendMessage() به راحتی از دو روش ارسال پیام یعنی کلاس های A و B استفاده کنید و حتی انواع دیگری نیز ایجاد نمایید و بدون تغییر در کلاس MessageApplication تنها از نوع جدید استفاده کنید.

مزایای dependency injection:

  • در dependency injection نیازی به compile مجدد کد ها وجود ندارد و در زمان اجرا compile می شوند.
  • کد های مورد استفاده در یک کلاس کاهش می یابد.
  • توسعه کدها به صورت آنلاین و استفاده از آنها در برنامه.

معایب تزریق وابستگی در جاوا:

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

کلمات کلیدی :
جاوا