تو یه پروژه ای که الان مشغولشم تصمیم گرفتم از داگر ۲ استفاده کنم و میخوام تجربیات خودم رو به همراه مفاهیم کلی تزریق وابستگی تو چند تا پست مطرح کنم.
بخش اول : مفاهیم اولیه
بخش دوم : ابزارهای تزریق وابستگی در جی وی ام
نخستین مفهمومی که باید بهش توجه کرد و تعریف کرد خود وابستگیه. فرض کنید کلاس سرویس کاربر برای انجام کارش نیاز به یک کلاینت اچ تی تی پی داره تا بتونه کار کنه. این تیکه کد کاتلین رو ببینید.
class HttpClient(private val address: String) class UserService{ private val clinet = HttpClient() }
تو این مثال یک کلاس کلاینت وجود داره که سرویس کاربر بهش احتیاج داره. بقیه چیزا رو عمدا ننوشتم که رو بحث وابستگی تمرکز کنیم. اینجا کلاس کلاینت رو تو کد خودمون داریم . اصلا مهم نیست کد خودمون باشه یا کتابخونه یا فریمورک های دیگه مهم اینکه کلاس سرویس وابستگی داره.
این کد بدون مشکل هم کامپایل میشه هم کار میکنه ولی مشکل کجاست. مشکل اصلی که تو دنیای امروز همه درموردش صحبت میکنن جداسازی و کاپل نبودن کلاس هاست. تو این کد فرض کنید به هر دلیلی بخوایم از یه کلاینت دیگه استفاده کنیم. واسه این کار حتما باید کلاس سرویس و احتمالا خیلی جاهای اون رو تغییر بدیم
مشکل دوم مساله بسیار مهم تست ه. شما مستقلا نمیتونید کلاس سرویس رو تست کنید چون حتما باید اون کلاس هم باشه
حالا میخوام همین کد رو یه خرده بهترش کنیم
interface Client {} class HttpClient(private val address: String):Client class UserService{ private lateinit var clinet:Client fun setClient(client:Client){ this.clinet = clinet } }
این بار یه اینترفیس اضافه کردیم و از کلاس کلاینت خودمون خواستیم اون رو پیاده سازی بکنه. تو این مثال اینترفیسمون رو خالی گذاشتیم که پیچیده نشه و خوانایی کد راحت تر باشه والا طبیعیه که تو دنیای واقعی اینترفیس اغلب متدهایی توش هست که کلاس هایی که از اون استفاده کنن باید اون رو پیاده کنن. حالا اگر بخوام از سرویس استفاده کنیم باید قبلش کلاینت رو به کلاس تحویل بدم یعنی ایجاد کلاینت بیرون از خود کلاس انجام میشه و میتونه هر نوع کلاسی باشه که اینترفیسمون رو پیاده کرده باشه. یه شیوه استفاد ش اینطوری میشه
val service = UserService() service.setClient(HttpClient("http://hemnhp.ir/api"))
این روش رو تزریق از طریق ستر میگن .تا اینجا یه خرده بهتر شد ولی بازم بدون مشکل نیستیم. یکیش اینه که اگر قبل از ست کردن به کلاینت احتیاج بشه به مشکل میخوریم یعنی کاربر هنوز ست نکرده باشه و ما لازم داشته باشیم. یکی هم اینه کلاسمون میوتیبل میشه و ساید افکت داره. این مفهوم رو بیشتر تو برنامه نویسی فانکشنال مدنظر قرار میدن. حالا یه خرده بازم بهترش کنیم
interface Client {} class HttpClient(private val address: String):Client class UserService(private val clint:Client){ }
تو این حالت وابستگی کلاس رو از طریق سازنده بهش پاس میدیم و مشکل قبلی رو حل کردیم
val service = UserService(HttpClient("http://hemnhp.ir/api"))
تو این حالت همه چی خوبه تا زمانی که کلاس وابستگی زیادی نداشته باشه که باعث بشه تعداد پارامترهای سازنده خیلی زیاد بشه. خب تا حالا مفهوم وابستگی و مفهوم تزریق رو بررسی کردیم. بخوایم از این هم جلوتر بریم باید الگوی طراحی فاکتوری رو مطرح کنیم . تو این الگو میگن اگر شما نیاز به ساخت نمونه از کلاس خاصی دارید وظیفه ساختنش رو به یک کلاس دیگه بدید — یعنی یک کلاس دیگه بنویسید که کارش صرفا همین باشه — حالا اگر این کارو هم بکنیم ساختن نمونه از کلاس برامون راحت میشه
اما اگه بخوایم تزریق وابستگی رو تو یه جمله راحت بیان کنیم میشه اینکه شما وظیفه ساخت و تزریق وابستگی های کلاسی که دارید مینویسید رو بدید دست یک نفر دیگه — یک کلاس یا کتابخونه یا فریمورک دیگه — یعنی دیگه خودتون مسولش نباشین .فرض کنید تو برنامه شما ۱۰۰ کلاس وجود داشته باشه و همه این ۱۰۰ کلاس رو انداخته باشین تو یه ظرف. حالا میخواین یک کلاس بنویسید که به سه تا از این کلاس ها وابستگی دارید. شما کافیه به مسول ظرف بگید که ما سه تا از اونا رو میخوایم . دیگه وظیفه تزریق اون وابستگی به عهده اون مسوله و شما متمرکز میشید رو بیزنس خودتون. فقط اینو بگم که مهمترین وظیفه اون مسول ترسیم یک گراف از وابستگی های کل برنامه ست که بتونه نمونه ها رو با در نظر گرفتن اولویت ها به کلاس ما برسونه . یعنی ممکنه که ما به کلاس الف نیاز داشته باشیم و خود الف به کلاس ب و الی آخر نیاز داشته باشه.به نظرم این سخت ترین کار اون مسوله