تبلیغات

Dependency Injection در جاوا – تزریق وابستگی در جاوا

Dependency Injection

همانطور که باید بدانید، Dependency Injection (تزریق وابستگی = DI)، یک الگوی طراحی (Design Pattern) معتبر، برای هر زبان برنامه‌‌نویسی می‌باشد. و همچنین مفهوم کلی پشت آن، Inversion of Control (معکوس سازی کنترل = IOC) نامیده می‌شود. و طبق این مفهوم، یک کلاس نباید وابستگی‌هایش را به صورت استاتیک پیکربندی کند (یعنی وابستگی hard-coded نداشته باشد)؛ بلکه باید آن‌ها از بیرون کلاس پیکربندی شوند.

برای مثال، اگر یک کلاس Car داشته باشیم، می‌تواند شامل ویژگی‌های Wheel, Engine و Battery باشد؛ حالا اگر برفرض داخل این کلاس، موتور آن را از کلاس PrideEngine ایجاد کرده‌ایم و در نتیجه کلاس Car ما بعد از کامپایل وابسته به موتور پراید خواهد بود! و اگر بخواهیم برفرض موقع رانتایم از PeugeotEngine استفاده کنیم، نمی‌توانیم!

بنابراین تزریق وابستگی به ما کمک می‌کند تا حد ممکن یک کلاس را مستقل طراحی کنیم؛ چرا که این موضوع، باعث می‌شود تا احتمال استفاده مجدد از آن کلاس افزایش یابد و بتوان آن را مستقل از سایر کلاس‌ها آزمایش کرد. و در واقع تزریق وابستگی، باعث می‌شود تا تجزیه و تحلیل وابستکی یک کلاس را از Complile-time به Runtime تغییر دهیم.

همچنین در این الگو، مفاهیم Loose Coupling (به حداقل رساندن وابستگی) و Tight Coupling نیز وجود دارند.

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

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

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

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

و در نهایت داخل متد main قصد داریم تا از کلاس MyApplication خود استفاده کنیم:

در نگاه اول، هیچ اشتباهیی در روش پیاده‌سازی بالا به چشم نمیخورد؛ اما منطق بالا، یک محدودیت خاصی دارد!

  • کلاس MyApplication، مسئول ایجاد سرویس ایمیل و سپس استفاده از آن است؛ که به وابستگی hard-coded منجر می‌شود! و اگر ما بخواهیم در آینده به سرویس پیشرفته‌تر ایمیل سوئیچ کنیم، به تغییر کد نوشته شده در کلاس MyApplication نیاز خواهیم داشت! که باعث میشود تا برنامه‌ی ما به سختی گسترش یابد و اگر سرویس ایمیل در کلاس‌های متعددی استفاده شود، این امر سخت‌تر نیز خواهد شد.
  • اگر بخواهیم کلاس اپلیکیشن خود را جهت فراهم کردن قابلیت‌های پیام‌رسانی دیگری از جمله ارسال اس ام اس، فیسیبوک و … نیز گسترش دهیم، ما نیاز خواهیم داشت تا یک اپلیکیشن دیگری برای آن بنویسیم! که باعث تغییرات کد در کلاس‌های اپلیکیشن و … خواهد شد.
  • تست و آزمایش کلاس اپلیکیشن بسیار دشوار است؛ چرا که اپلیکیشن ما به صورت مستقیم نمونه‌ی سرویس ایمیل را ایجاد می‌کند؛ و در نتیجه هیچ راهی وجود ندارد تا یتوانیم این اشیاء را با کلاس‌های آزمایشی (test)، تقلید کنیم. (یا به عبارتی mock کنیم). (در ادامه این کار را با تزریق وابستگی انجام خواهیم داد)

بنابراین می‌توان از این موارد گفته شده، استدلال کرد که ما می‌توانیم ایجاد نمونه‌ای از کلاس EmailService که داخل کلاس اپلیکیشن وجود دارد، را با در اختیار داشتن یک متد سازنده‌ای که EmailService را به عنوان ورودی دریافت می‌کند، حذف کنیم:

ولی در اینجا ما از بیرون می‌خواهیم تا کلاس EmailService را مقداردهی کند که متاسفانه این تصمیم درستی برای طراحی نیست!


حالا  ببینید که چگونه ما می‌توانیم الگوی تزریق وابستگی جاوا را برای حل همه‌ی مشکلات موجود در پیاده‌سازی بالا به کار ببریم؛ و همچنین تزریق وابستگی در جاوا به حداقل موارد زیر نیاز دارد:

  • کامپوننت‌های سرویس (مانند EmailService)، باید با استفاده از یک Base Class (کلاس پایه/پدر) یا اینترفیس (Interface) طراحی شوند. و بدین منظور استفاده از  اینترفیس‌ها یا کلاس‌های انتزاعی (abstract) بهتر است.
  • پکلاس‌های مصرف کننده (مثل MyApplication در بالا)، باید طبق ساختار اینترفیس یا کلاس انتزاعی نوشته شوند.
  • ساخت کلاس‌های Injector (تزریق‌کننده) که سرویس‌ها را مقداردهی کرده و سپس کلاس‌های مصرف کننده را اجرا می‌کنند.

کامپوننت‌های سرویس

برای مثالی که در نظر گرفته‌ایم، ما می‌توانیم یک اینترفیسی با نام MessageService داشته باشیم که قرار دادی را برای پیاده‌سازی سرویس تعریف و اعلام می‌کند:

حالا اجازه دهید که بگوییم ما سرویس‌های ایمیل و اس‌ام‌اس را در اختیار داریم که واسط (اینترفیس/رابط) بالا را اجرا می‌کنند:

سرویس‌های جاوای تزریق وابستگی ما حالا آماده هستند! و حالا باید consumer class (کلاس مصرف کننده) را بنویسیم.

کلاس مصرف کننده

ما لازم نیست که رابط‌های پایه (base interfaces) را برای کلاس مصرف کننده داشته باشیم؛ اما یک اینترفیس مصرف کننده برای کلاس‌های مصرف کننده خواهیم داشت.

و پیاده سازی کلاس مصرف کننده نسبت به این اینترفیس:

توجه داشته باشید که کلاس اپلیکیشن ما، فقط از سرویس (که در بالا MessageService می‌باشد) استفاده می‌کند و آن را مقداردهی اولیه (initialize) نکرده است. و همچنین استفاده از این اینترفیس سرویس (در اینجا منظور MessageService)، به ما این اجازه را می‌دهد تا به راحتی اپلیکیشن خود را با تقلید کردن MessageService، تست و آزمایش کنیم. (یعنی یک کلاس، همانند EmailServiceImpl و SMSServiceImpl بسازیم که به صورت آزمایشی نه واقعی کاری را انجام دهد و نسبت به آن اپلیکیشن خود را تست کنیم! مثلا نیازی به ارسال ایمیل واقعی نباشد و بخواهیم صرفا به صورت آزمایشی بنویسیم که ایمیل ارسال شد.)


اکنون آماده هستیم تا کلاس‌های اینجکتور (تزریق کننده) خود را بنویسیم تا سرویس‌ها را مقداردهی کرده و مصرف کننده را اجرا کنند.

کلاس‌های تزریق‌کننده (Injectors Classes)

بیایید یک رابط (اینترفیس) MessageServiceInjector بنویسیم که متدی برای تعریف کلاس مصرف کننده داشته باشد.

اکنون برای هر سرویس، باید کلاس‌های تزریق‌کننده را مانند زیر ایجاد کنیم:

و حالا بییایید تا ببینیم چگونه از این طراحی در برنامه می‌خواهیم استفاده کنیم:

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

آزمون JUnit و با Mock Injector و سرویس

همانطور که می‌بینید، من از کلاس‌های ناشناس (anonymous) برای تقلید تزیرق کننده (mock کردن injector) و کلاس سرویس استفاده کرده‌ام. و من به راحتی توانستم متدهای اپلیکیشن خود را تست کنم. و همچنین در اینجا از JUnit 4 استفاده کردم و بنابراین مطمئن شوید که آن را در پروژه‌ی خود به کار برده‌اید.

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

ما در کدهای بالا از سازنده‌ها برای تزریق وابستگی‌ها در کلاس‌های اپلیکیشن استفاده کرده‌ایم؛ راه دیگر استفاده از متدهای setter است. برای مثال در اینصورت کلاس MyDIApplication  را به صورت زیر می‌نویسیم:

و اینجکتور ایمیل:

نکات تکمیلی

  • یکی از بهترین مثال‌های تزریق وابستگی با استفاده از setter ها، مثال « … – به زودی لینک خواهد شد» می‌باشد.
  • استفاده از متدسازنده یا متدهای ستر برای تنظیم وابستگی، یک تصمیم طراحی بوده و به نیازها و ملزومات شما بستگی دارد. برای مثال، اگر برنامه من نمی‌تواند بدون کلاس سرویس کار کند، بنابراین من باید متدسازنده‌ای ایجاد کنم که وابستگی را دریافت کند و در غیر این صورت، می‌توانم از setter استفاده کنم.
  • تزریق وابستگی در جاوا، یک راه برای به دست آوردن Inversion of control (IoC) یا «معکوس سازی کنترل» در اپلیکیشن‌هایمان است؛ که این کار را با انتقال Binding آبجکت‌ها از زمان کامپایل به رئال‌تایم انجام می‌دهد.
  • فریمورک‌های Spring Dependency Injection، Google Guice و Java EE CDI ، روند تزیرق وابستگی را با استفاده از رفلکشن‌ها (API Reflection Java) و annotations ها برایمان تسهیل می‌کنند.

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

برخی از مزایای انجام این کار:

  • Separation of Concerns یا تفکیک دغدغه‌ها
  • کاهش Boilerplate code (بویلرپلت)
    به دلیل اینکه تمامی کار مقداردهی وابستگی‌ها، توشظ کامپوننت تزریق‌کننده مدیریت می‌شود.
  • توسعه‌پذیر کردن کامپوننت‌ها
  • تست و آزمایش آسان

تزریق وابستگی در جاوا شامل معایبی نیز می‌باشد:

  • در صورت استفاده بیش از حد، می‌تواند منجر به مشکلات تعمیر و نگهداری کد (maintenance) شود؛ چراکه اثر تغییرات در زمان ران‌تایم شناخته می‌شوند.
  • تزریق وابستگی در جاوا، وابستگی‌های کلاس سرویس را مخفی می‌کند که می تواند خطاهای زمان اجرا را که در زمان کامپایل شدن رخ داده است، منجر شود.
تبلیغات
0
کانال تلگرام فول کده
تبلیغات

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

هادی اکبرزاده

[ مدیر فول کده ]

علاقه‌مند به اشتراک گذاری اطلاعات در هر زمینه‌ای / برنامه‌نویس / مدیر فول کده

پاسخ دهید

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

نظرات ثبت شده بدون دیدگاه