در این نوشته گپ مختصری پیرامون مفهوم تزریق وابستگی میزنیم. در قسمت های بعدی به بررسی چند مثال در قالب کد میپردازیم.
برای شروع بهتره در مورد یک اصل مهم صحبت کنیم. اصلی با عنوان Hollywood Principle.
در واقع یک اصل ساده س! اینجوریه که توی هالیوود وقتی برای یک فیلم بخوان بازیگر استخدام کنند، خودِ اون بازیگر هیچوقت زنگ نمیزنه به هالیوود و بگه که منو انتخاب کنید :)) بطور کلی یک شعار مهم دارن که میگه:
Don’t call us! We'll call you!
همیشه اینجوریه که هالیوود با اشخاص تماس میگیره. حالا همین بحث ساده میاد توی مهندسی نرم افزار و تبدیل میشه به یک بحثِ بنیادی که بر اساس اون به این معنا میرسیم:
"من قرار نیست خودم کاری رو انجام بدم! من تنها کارهایی که ازم خواسته بشه رو انجام میدم."
بنابراین Hollywood Principle اینجوری به کارِ ما میاد که به هر کدوم از Entity هایی که توی سیستم داریم، میگیم که این کلاس ها لازم نیست خودشون بگن که به چه Object هایی بعنوان Dependency نیاز دارند. اون ها فقط کافیه یک توصیف اولیه ازشون داشته باشند. ما هر زمانیکه به اون Object ها نیاز داشته باشیم، خودمون مسئولیت تزریق کردنشون رو به عهده میگیریم. دقیقا در همین نقطه هستش که با مفهومی با عنوانِ Inversion Of Control (IOC) مواجه میشیم.
به طور کلی IOC به این معناست که کنترل، به جای اینکه دستِ خودِ Object باشه، میاد دستِ ما. بنابراین بجای اینکه خودِ Object تصمیم بگیره که چطوری Dependency هاشو بسازه، ما این کنترل رو بدست میگیریم. در این نقطه ما میتونیم فرضا بعنوان یک فریمورک، که از بالا به همه چیز مسلطه، تصمیم بگیریم که هر کدام از وابستگی ها به چه شکلی ساخته بشه.
احتمالا همگی فرق بین Library و Framework رو بدونیم. Hollywood Principle به نوعی فرق بین این دو را به خوبی بیان میکنه. در واقع فریمورک ها همگی این اصلِ Hollywood Principle رو درونِ خودشون دارند، در حالی که Library ها خیر.
میدونیم که Library ها مجموعه ای از Function ها هستند که ما از اون ها برای یک هدفی، داخلِ کدمون استفاده میکنیم. در حالیکه در بحثِ فریمورک ها، به ما یکسری Functionality و یا Extension Point داده میشه که ما میتونیم بر اساس اون ها، پیاده سازی خودمون رو دنبال کنیم. در نهایت میتونیم ببینیم که بر خلاف Library ها، این ما نیستیم که از فریمورک ها استفاده میکنه! در واقع این فریمورک ها هستند که از کدهای ما استفاده میکنند.
معمولا از مباحثی که تا اینجا مطرح شد با عنوان IOC یاد میشه، که سطوح مختلفی داره و برای کارهای مختلفی میشه ازش استفاده کرد. یکی از این سطوح، سطحِ ساختنِ Object هستش که در این سطح، با مفهوم DI مطرح میشه که به نوعی همان اصلِ Hollywood Principle رو اجرایی میکنه.
در این نقطه ما یک کلاس رو با این تفکر طراحی میکنیم که انگار کل دنیا همون یک کلاسه و بدون هیچ گونه وابستگی ای. به نوعی ما در این مرحله، هیچگونه تصمیمی در مورد اینکه وابستگی ها به چه شکلی پیاده سازی میشن نمیگیریم. فقط میگیم که:
"من انتظار دارم که بعنوان ورودی، یکسری Object از یک کلاس پاس داده بشه تا بتونیم ازشون استفاده کنیم."
زمانیکه بخواهیم کلاسی رو پیاده سازی کنیم قاعدتا با یک Specification طرفیم. به نوعی یک مفهومی داریم که قراره ازش یک پیاده سازی داشته باشیم. موضوع اینه که پیاده سازی ای که داریم، نباید به یک پیاده سازیِ دیگه تویِ سطحِ خودش وابستگی داشته باشه. در واقع این پیاده سازی تنها باید وابستگی داشته باشه به High Level Specification هایی که داریم. به این معنی که وقتی داریم کلاسی رو پیاده سازی میکنیم، نباید Dependency داشته باشیم به یک کلاس دیگه ای که داخل Specification ما هست. برای مثال میتونیم یک Interface داشته باشیم که ما وابسته بشیم به اون.
با پیروی از این اصول، ما وقتیکه داریم پروژه رو Run میکنیم، میتونیم تصمیم بگیریم که کدامیک از پیاده سازی های این Specification باید توی سیستم کار بکنه! حالا اگر این الگو رو رعایت نکرده باشیم و وابستگی داشته باشیم دقیقا به یک کلاسِ خاص، که یکی از این Specification ها رو پیاده سازی کرده، در این حالت فرضا توسعه دهنده ای که بخواد سیستم رو کانفیگ کنه یا هر کسی که بخواد پروژه رو Run کنه، این امکان رو نداره که یک پیاده سازی دیگه ای از اون Specification که نیاز داشته رو استفاده کنه. چی شد؟ بهتره مثالِ زیر رو بررسی کنیم.
فرض کنید بخواهیم اطلاعات مشتری رو از روی یک دیتابیس دولتی بخوانیم. در این حالت فرض میکنیم که یک وب سرویس در اختیار داریم که قراره اون رو صدا بزنیم و اطلاعات رو ازش بگیریم. حالا اگر موقعِ پیاده سازی، وابستگی ایجاد کرده باشیم به اون پیاده سازی ای که مخصوصِ همین دیتابیسه، با این کار، این شانس رو از تیم توسعه گرفتیم که بدون اینکه کلاسِ ما رو تغییر بدن، از اون وب سرویس استفاده کنند. این مساله تنها در پروژه های کوچک ممکنه مشکل حادی بوجود نیاره، اما موضوع Open Close Principle (OCP) در این مورد نقض میشه و برای پروژه های بزرگ قطعا به مشکل میخوریم.
- طراحی با رعایت IOC کمک میکنه که نوشتن Unit Test بسیار ساده تر بشه. به نوعی با بهبود Testability مواجه خواهیم شد.
- برای ما توسعه ی کد رو راحت تر میکنه که در نتیجه ی اون، دیگه قرار نیست تغییرات زیادی در کد داشته باشیم. اصطلاحا کدمون Pluggable میشه. به این معنا که میتونیم کد رو در اختیار تیم دیگه ای بذاریم و اونها تنها با Customize کردن قسمت های بسیار کمی، از کدِ ما برای پروژه های خودشون استفاده کنند.
OH! My Talks!