مقدمه
زمانیکه یک برنامه را بر پایهی شیء گرایی طراحی میکنید و مینویسید، به صورت معمول جریان وابستگیها در برنامهی شما به صورت زیر است:
در این حالت برای کامپایل شدن برنامه نیاز است که فرآیند کامپایل از دورترین کلاس و متد شروع شود. همانطور که میبینید در اینجا هر کلاس به تمام زیر کلاسهای خود وابسته است و هر تغییر در هر کدام از کلاسهای خدمتگزار میتواند تاثیرات مستقیمی بر روی سایر کلاسها داشته باشد. در واقع جریان کنترل برنامهی ما بجای اینکه در اختیار کلاسهای سیاست گذار ( کلاسهای بالایی در شکل) باشد، به دست کلاسهای خدمتگزار افتاده است. این قضیه باعث درهم تنیدگی و جفت شدگی کدها و کلاسها به یکدیگر میشود که مشکلزاست و امکان نگهداری، تغییرات و توسعهی برنامه را به شدت کاهش میدهد.
به عبارت دیگر در طراحی ساخت یافته، کلاسهای سطح بالا، به کلاسهای سطح پایین وابستهاند. این مسئله دو مشکل را ایجاد میکند:
-
هر تغییری در کلاسهای سطح پایین ممکن است باعث ایجاد اشکالی در کلاسهای سطح بالا گردد.
-
استفادهی مجدد از کلاسهای سطح بالا در جاهای دیگر مشکل است؛ زیرا وابستگی مستقیمی به کلاسهای سطح پایین دارند.
اصل معکوس سازی / وارونگی وابستگیها Dependency Inversion Principle
یکی از اصول پنجگانهی طراحی برنامههای شیء گرا که با نام اصول SOLID شناخته میشوند، اصل «وارونگی وابستگیها» است که روشی برای مشکل جفت شدگی و وابستگی کلاسها به یکدیگر را به صورت تئوری ارائه میدهد.
اصل وارونگی وابستگیها بیان میکند:
-
ماژولهای (کلاسهای) سطح بالا نباید به ماژولهای (کلاسهای) سطح پایین وابسته باشند و هر دو باید به انتزاعات وابسته باشند (برای مثال interfaceها).
- انتزاعات نباید وابسته به جزئیات باشند؛ بلکه جزئیات (پیاده سازی) باید وابسته به انتزاعات باشند.
بر اساس این اصل، ما باید در سطوح بالا سیاست گذاریها و انتزاعات را در قالب interfaceها تعریف کرده و کلاسهای سطح بالای خود را بر همین اساس پیاده سازی کنیم و در سطوح پائینتر، پیاده سازیهایی را بر اساس انتزاعات و سیاست گذاریهای سطوح بالاتر، انجام دهیم.
در شکل زیر، حالت عادی جریان کنترل را میبینید .
همانطور که میبینید، کلاس M برای اجرا، وابسته به کلاس N و متد F در درون آن است. در اینجا ما با استفاده از اینترفیسها میتوانیم جریان کنترل را معکوس یا وارونه کنیم که به این عمل «وارونگی کنترل یا Inversion of Control» میگویند.
شیء گرایی در واقع در مورد نحوهی جریان کنترل است. در اینجا اینترفیسها به ما کنترل کاملی را بر جریان کنترل (Flow of control) میدهند که با استفاده از این امکان میتوانیم از نوشتن کدهای جفت شده، شکننده و کلاسهایی یکبار مصرف، بپرهیزیم.
الگوی Dependency Injection
تزریق وابستگی یا Dependency Injection، یک الگوی طراحی است که از آن برای طراحی و پیاده سازی IoC Containerها استفاده میشود. این الگو به ما اجازه میدهد که اشیاء وابسته را خارج از کلاس بسازیم و آنها را به طریقی دیگر به کلاس، جهت استفاده ارائه دهیم. بهوسیلهی DI ما ساخت و اتصال اشیاء وابسته به کلاس را از تعریف آن خارج میکنیم.
الگوی تزریق وابستگی 3 نوع کلاس را درگیر میکند:
- کلاس کلاینت / Client Class : کلاس کلاینت (کلاس وابسته) کلاسی است که به کلاس سرویس وابسته است .
- کلاس سرویس / Service Class : کلاس سرویس (وابستگی) کلاسی است که یک سرویس را به کلاس کلاینت ارائه میدهد.
- کلاس تزریق کننده / Injector Class : کلاس تزریق کننده، نمونهای از کلاس سرویس را ساخته و به کلاس کلاینت، تزریق میکند.
شکل زیر وابستگی بین کلاسها را شرح میدهد:
همانطور که میبینید، کلاس Injector، نمونهای از کلاس سرویس را میسازد و آن را به نمونهای از کلاس Client تزریق میکند. با این کار، DI، وظیفهی ساخت یک نمونه از کلاس Service را از درون کلاس Client جدا میکند.
انواع تزریقات وابستگیها:
به صورت کلی به سه روش و در سه مکان، امکان تزریق وابستگی کلاس سرویس، درون کلاس کلاینت وجود دارد:
-
تزریق درون سازنده / Constructor Injection : در تزریق درون سازنده، در سازندهی کلاس کلاینت، لیستی از سرویسهای مورد نیاز کلاس، که کلاس، برای عملکرد خود به آنها «وابسته» است، ثبت میشوند و کلاس Injector، سرویس (وابستگی) مورد نظر را درون سازندهی کلاس Client ارائه میدهد.
-
تزریق درون Property کلاس / Property Injection : در این حالت که همچنین با نام (Setter Injection) هم شناخته میشود، تزریق کننده، وابستگی را به وسیلهی یک Property عمومی کلاس کلاینت ارائه میدهد.
- تزریق درون متد / Method Injection : در این حالت، خود کلاس کلاینت، یک پیاده سازی از یک interface را ارائه میکند که درون آن متدهایی برای ارائهی وابستگیها به کلاینت تعریف شدهاند. در این وضعیت، تزریق کننده از این اینترفیس برای ارائهی وابستگیها به کلاینت درون متدها، استفاده میکند.
هر کدام از روشهای فوق مزایا و معایب خود را دارند، ولی در NET Core. بیشتر از «تزریق درون سازنده» استفاده میشود. در صورت لزوم میتوانید از
اینجا نمونههایی از تزریق وابستگی را به هر کدام از سه روش بالا، مشاهده کنید.
Inversion of Control Container
در واژگان فنی مهندسی نرم افزار، Container (محفظه) به جزیی از برنامه گفته میشود که میتواند اجزای دیگر برنامه را در بر بگیرد. IoC Container ها در واقع فریم ورکها/چارچوبها یا کتابخانههای برنامه نویسی هستند که ما در آنها میتوانیم اشیاء مختلف را به سبکهای خاصی تعریف و ثبت کنیم و در مواقع لزوم آنها را واکشی و به کلاسها تزریق کنیم. معمولا IoC Containerها لیستی از اشیاء هستند که در آن اینترفیسها و پیاده سازیهای مربوط به هر کدام از آنها ثبت میشوند. درون IoC Container برای پیاده سازی اصل وارونگی وابستگیها، معمولا از یکی از دو الگوی زیر استفاده میکنند (گاها هم دو الگو را با هم پیاده سازی میکنند) :
- Dependency Injection
-
Service Locator
تمرکز اصلی ما در این نوشتار بر روی DI Container هاست. فرق Dependency Injection و Service Locator در این است که در DI، وابستگیها توسط IoC Container از درون محفظه واکشی میشوند و به درون کد تزریق میشوند ولی در Service Locator در هر جایی از برنامه میتوان با استفادهی مستقیم از Container و با استفاده از متدهایی که به ما ارائه میدهد، پرس و جو کرد (کوئری زد) و وابستگی مورد نظر را واکشی کرد.
در تزریق وابستگی، کلاس استفاده کننده از سرویسها، درگیر نحوهی واکشی و نمونه سازی از سرویس مورد نظر خود نمیشود و همهی کار توسط DI Container انجام میگیرد. ولی در Service Locator باید سرویس مورد نظر، درون خود کلاس، مستقیما از Container دریافت و ساخته شود.
برای استفاده از Service Locator، تنها پیش نیاز، دسترسی به شیء Service Locator است.
به صورت کلی IoC Container ها سه وظیفهی اساسی را برعهده دارند:
-
ثبت سرویس درون خود
- ساخت نمونههای مورد نظر از سرویسها و ارائه دادن آنها به کلاسهایی که نیاز دارند.
- از بین بردن نمونه سرویسهای ساخته شده (Dispose) کردن آنها .
در ادامه با ساخت پروژهای، اولین سرویس خودمان را درون Microsoft Dependency Injection Container یا به اختصار DI Container، ثبت کرده و آن را واکشی میکنیم.