زمان تخمینی مطالعه: 10 دقیقه
مقدمه
در واقع Dependency Injection یک قرارداد طراحی است که وابستگیهای کد را با استفاده از اصل وارونگی کنترل (inversion of control-IOC) حل میکند. این الگو ارتباط یکپارچهای بین اجزای نرم افزاری سازگار ایجاد میکند. توجه به این نکته مهم است که چالش حیاتی در توسعه نرم افزار، مدیریت و درک تزریق وابستگی است. توسعهدهندهای که از الگوی طراحی(Design Pattern) استفاده میکند، کد قابل خواندن، کارآمد و انعطافپذیر تولید میکند.
مفهوم Dependency Injection چیست؟
تزریق وابستگی (Dependency Injection) یک روش برنامه نویسی است که ایجاد و استفاده از اجزا را از هم جدا میکند. الگوی طراحی(DP) در برنامه نویسی شیگرا ظاهر شده و به اصول SOLID (مخصوصاً S و D) پایبند است. ایدههای مشابه در مورد سایر پارادایمهای برنامه نویسی مانند برنامه نویسی امری یا اعلانی نیز اعمال میشود. Dependency Injection شامل دو کلمه کلیدی است که به ابهامزدایی از مفهوم آن کمک میکند:
- وابستگیها: اجزای کد برای انجام وظایف خود به بسیاری از اشیا و خدمات (وابستگیها) متکی هستند. وابستگیهای مختلفی مانند منابع خارجی، اشیاء دیگر یا خدمات وجود دارد.
- تزریق: کامپوننتها وابستگیهای داخلی را در Dependency Injection ایجاد یا پیوند نمیدهند. در عوض، این تکنیک وابستگیها را از بیرون فراهم میکند (تزریق میکند). این رویکرد بین اجزا و وابستگیها جدایی ایجاد میکند.
وابستگیها در فرمها و اشکال مختلفی وجود دارند. آنها برای کارکرد یک جزء ضروری هستند اما بخشی از خود جزء نیستند. API ها، کتابخانهها، پایگاههای داده و سایر مؤلفهها همگی میتوانند به عنوان وابستگی عمل کنند. تزریق وابستگی چالش مدیریت وابستگی را حل میکند. با جدا کردن مدیریت وابستگی از یک جزء، یک منبع خارجی وابستگیها را ایجاد و مدیریت میکند.
جداسازی (decoupling) تعداد اتصالات بین اجزا و ماژولها را کاهش میدهد. اتصالات کمتر منجر به تعمیر و نگهداری ساده و انعطاف پذیری میشود. در نتیجه، کد قابل استفاده مجدد میشود زیرا مؤلفهها مستقل هستند و در زمینههای مختلف قابل استفاده هستند. تیمهای توسعه دیگر نیازی به هماهنگی مداوم ندارند و میتوانند ماژولها را به صورت موازی توسعه دهند.
چرا از Dependency Injection استفاده کنیم؟
دلایل بسیاری برای استفاده از تزریق وابستگی(DI) وجود دارد. برخی از مهمترین مزایای آن عبارتند از:
- خوانایی: Dependency Injection تقسیم بین منطق یک جزء و وابستگیهای آن را برجسته میکند. با استفاده از مفهوم DI کد نوشته شده خوانا تر شده و سازماندهی بهتری دارد.
- قابلیت استفاده مجدد: با استفاده از تکنیک DI اجزای دارای وابستگی تزریق شده به دلیل مستقل بودن به راحتی برای پروژههای مختلف قابل استفاده مجدد هستند. Dependency Injection یک رویکرد قالببندی برای کدنویسی، کاهش بلوکهای کد مکرر و بازنویسی در پروژههای مختلف را ارائه میکند.
- آزمایش پذیری: تزریق وابستگی نوشتن تستهای واحد را برای اجزای جداگانه ساده میکند. آزمایش منظم منجر به کد قابل اعتمادتر و با اشکالات کمتر میشود.
- قابلیت نگهداری: به دلیل ارتباط کمتر بین مؤلفهها و ماژولها، تغییر یا بهبود تک تک قسمتهای کد تأثیری بر سایر قسمتهای کد ندارد.
انواع Dependency Injection کدامند؟
توسعه دهندگان از تکنیکهای مختلفی برای اجرای Dependency Injection استفاده میکنند. در انتخاب آن رویکردی را انتخاب کنید که به بهترین وجه با الزامات، زبان برنامه نویسی و موارد استفاده مطابقت دارد. در ادامه مروری مختصر و نمونهای از انواع Dependency Injection آورده شده است:
- Constructor Injection: یک نوع تزریق وابستگی است که وابستگیها را از طریق سازنده تزریق میکند. هنگامی که یک نمونه از یک کلاس یا شی ایجاد میشود، سازنده(Constructor) وابستگیها را در طول ایجاد فراهم میکند. در نتیجه کلاس یا شی به درستی اجرا میشود و بلافاصله تمام وابستگیهای لازم را دارد. هنگامی که وابستگیها برای عملکرد یک کلاس یا شی مورد نیاز است از تزریق سازنده(Constructor Injection) استفاده کنید. در این نوع DI وابستگیها در طول نمونه سازی کلاس یا شی تزریق میشوند و وابستگیها را بلافاصله در دسترس یک نمونه کلاس یا شی قرار میدهند.
- Setter Injection: تزریق Setter یک Dependency Injection است که وابستگیها را از طریق متدهای setter یا ویژگیهای کلاس فراهم میکند. یک توسعه دهنده پس از نمونه سازی شی یا کلاس، وابستگیها را به صورت دستی به روز میکند. در نتیجه زمانی که متدهای تنظیم کننده فراخوانی میشوند، شی وابستگیهای مناسبی دریافت میکند. در شکل کلی هنگامی که یک کلاس یا شیء بدون وابستگی اجباری عمل میکند، از Setter Injection استفاده کنید. در این حالت، وابستگیها در طول نمونه سازی اختیاری هستند و میتوانند پس از اجرا تزریق شوند. این رویکرد اجازه میدهد تا پس از ایجاد شی، وابستگیها را به صورت پویا تغییر دهید.
- Method Injection: تزریق متد (یا تزریق پارامتر) یک نوع تزریق وابستگی است که وابستگیها را به عنوان پارامترهای متد ارائه میدهد. کلاس یا شیء هنگام فراخوانی یک متد، انتظار وابستگیها را به عنوان آرگومان دارد. از تزریق متد برای تزریق وابستگی ریز دانه(fine-grained dependency injection) بر اساس متدهای مجزا استفاده کنید. روش Method Injection به بالاترین درجه جداسازی(decoupling) دست پیدا کرده و زمانی مفید است که یک کلاس چندین متد دارد که به وابستگیهای یکسانی نیاز ندارند.
Dependency Injection چگونه کار میکند؟
فرآیند تزریق وابستگی از چهار جزء اصلی (نقش) استفاده میکند:
- خدمات(Service): مؤلفهای که عملکرد یا سرویسی را ارائه میدهد. سایر اجزا به خدمات ارائه شده بستگی دارد.
- مشتری(Client): جزء یا کلاسی که برای انجام وظایف به سرویس بستگی دارد. مشتری سرویس را ایجاد یا مدیریت نمیکند. برای ارائه خدمات به فرآیند Dependency Injection متکی است.
- تزریق کننده(Injector): نمونههای سرویس را ایجاد و مدیریت میکند و آنها را به مشتریان تزریق میکند. تزریق کننده تمام وابستگیها را برطرف کرده و قطعات را در صورت لزوم به هم متصل میکند.
- رابط(Interface): رابط (یا قرارداد) متدها یا ویژگیهایی که یک سرویس پیاده سازی میکند را تعریف میکند. مفهوم انتزاع توافقی است بین خدمات و مشتری، که تضمین میکند این سرویس به تمام قوانین و انتظارات پاسخ میدهد.
فرآیند Dependency Injection مراحل زیر را طی میکند:
- تعریف وابستگیها: منابعی را که کلاسها و مؤلفهها برای عملکرد نیاز دارند را تعیین کنید.
- تعریف واسطها (قراردادها): برای هر وابستگی یک رابط ایجاد کنید. متدها یا ویژگیهایی را که به عنوان قواعد قرارداد عمل میکنند ارائه دهید.
- تزریق وابستگیها: مکانیزمی را برای تزریق وابستگیها به کلاسها یا مؤلفهها انتخاب کنید. بسته به مکانیسم، این مرحله به صورت دستی، با یک کانتینر DI یا یک چارچوب انجام میشود.
- استفاده از وابستگیها: کلاسها و کامپوننتها با تعامل با قراردادهای تعریف شده از وابستگیها استفاده میکنند.
مزایا و معایب Dependency Injection
در هنگام اجرای تزریق وابستگی هم مزایا و هم معایبی وجود دارند. اگرچه این رویکرد قابلیت نگهداری کد را بهبود میبخشد اما منحنی یادگیری دارای شیب زیادی است و پیچیدگی کد با استفاده از DI بالا خواند رفت. در ادامه مروری کوتاه بر مزایا و معایب اصلی Dependency Injection خواهیم داشت:
– مزایا:
- جدایی منطقی: اتصال آزاد بین اجزا و وابستگیها خواندن و نگهداری کد را آسان تر میکند. کلاسها و اشیاء نمونه سازی یا مدیریت وابستگی را مدیریت نمیکنند.
- تست بهبود یافته: با استفاده از DI یونیت تست ساده تر میشود. ایجاد وابستگیهای ساختگی یا آزمایش اجزای فردی در این روش ساده شده است.
- مدیریت وابستگی انعطاف پذیر: جداسازی وابستگیها و مولفهها انعطاف پذیری را فراهم میکند. وابستگیهای مختلفی را میتوان تزریق کرد، که محیطی به راحتی سازگار ایجاد میکند.
- اجزای قابل استفاده مجدد: به دلیل اتصال ضعیف بین اجزا و وابستگیها، کد در زمینهها و محیطهای مختلف قابل استفاده مجدد است.
- تعمیر و نگهداری ساده تر: ارتقاء کتابخانهها یا مؤلفهها بر کلاس وابسته اساسی تأثیر نمیگذارد.
- توسعه همزمان: توسعه دهندگان میتوانند همزمان با پایبندی به قراردادهای تعریف شده، روی ماژولها و وابستگیها به صورت موازی کار کنند.
– معایب:
- پیچیدگی: هنگام مدیریت بسیاری از وابستگیها یا پیکربندیهای پیچیده، DI علاوه بر این پیچیدگی کد را نیز افزایش میدهد.
- منحنی یادگیری: درک کامل مفاهیم پشت DI و زمان بکارگیری آنها زمان میبرد. توسعه اولیه پروژه به دلیل منحنی یادگیری کند میشود.
- سربار: روش Dependency Injection برای پروژههای کوچکتر مناسب نیست، زیرا سربار اضافی ایجاد میکند.
- خطاهای زمان اجرا: وابستگیهایی که با دقت تزریق نمیشوند منجر به خطاهای زمان اجرا میشوند. عیب یابی به ویژه در محیطهای پیچیده چالش برانگیز است.
مثال عملی Dependency Injection
– Constructor Injection: همانطور که قبلا ذکر شد، زمانی که وابستگی را از طریق سازنده ارائه میکنیم، به آن Constructor Injection میگویند.مثال زیر را در نظر بگیرید که در آن DI را با استفاده از سازنده پیاده سازی کردهایم:
public class CustomerBusinessLogic
{
ICustomerDataAccess _dataAccess;
public CustomerBusinessLogic(ICustomerDataAccess custDataAccess)
{
_dataAccess = custDataAccess;
}
public CustomerBusinessLogic()
{
_dataAccess = new CustomerDataAccess();
}
public string ProcessCustomerData(int id)
{
return _dataAccess.GetCustomerName(id);
}
}
public interface ICustomerDataAccess
{
string GetCustomerName(int id);
}
public class CustomerDataAccess: ICustomerDataAccess
{
public CustomerDataAccess()
{
}
public string GetCustomerName(int id)
{
//get the customer name from the db in real application
return "Dummy Customer Name";
}
}
در مثال بالا، CustomerBusinessLogic شامل سازنده با یک پارامتر از نوع ICustomerDataAccess است. اکنون، کلاس فراخوان باید یک شی از ICustomerDataAccess را تزریق کند.
public class CustomerService
{
CustomerBusinessLogic _customerBL;
public CustomerService()
{
_customerBL = new CustomerBusinessLogic(new CustomerDataAccess());
}
public string GetCustomerName(int id) {
return _customerBL.ProcessCustomerData(id);
}
}
همانطور که در مثال بالا میبینید، کلاس CustomerService شی CustomerDataAccess را ایجاد کرده و به کلاس CustomerBusinessLogic تزریق میکند. بنابراین، کلاس CustomerBusinessLogic نیازی به ایجاد یک شی از CustomerDataAccess با استفاده از کلمه کلیدی جدید یا با استفاده از کلاس کارخانه(Factory) ندارد. کلاس فراخوان (CustomerService) کلاس DataAccess مناسب را برای کلاس CustomerBusinessLogic ایجاد و تنظیم میکند. به این ترتیب، کلاسهای CustomerBusinessLogic و CustomerDataAccess به کلاسهای «بیشتر» تبدیل میشوند که با هم جفت شدهاند.
– Setter Injection: در این نوع تزریق، وابستگی از طریق ویژگی عمومی تامین میشود. مثال زیر را در نظر بگیرید:
public class CustomerBusinessLogic
{
public CustomerBusinessLogic()
{
}
public string GetCustomerName(int id)
{
return DataAccess.GetCustomerName(id);
}
public ICustomerDataAccess DataAccess { get; set; }
}
public class CustomerService
{
CustomerBusinessLogic _customerBL;
public CustomerService()
{
_customerBL = new CustomerBusinessLogic();
_customerBL.DataAccess = new CustomerDataAccess();
}
public string GetCustomerName(int id) {
return _customerBL.GetCustomerName(id);
}
}
همانطور که در بالا میبینید، کلاس CustomerBusinessLogic شامل ویژگی عمومی به نام DataAccess است که در آن میتوانید نمونهای از کلاسی را تنظیم کنید که ICustomerDataAccess را پیاده سازی میکند. بنابراین، کلاس CustomerService با استفاده از این ویژگی عمومی، کلاس CustomerDataAccess را ایجاد و تنظیم میکند.
– Method Injection: در روش تزریق متد، وابستگیها از طریق متدها فراهم میشود. این متد میتواند یک متد کلاس یا یک متد رابط باشد. مثال زیر تزریق متد را با استفاده از روش مبتنی بر رابط نشان میدهد:
interface IDataAccessDependency
{
void SetDependency(ICustomerDataAccess customerDataAccess);
}
public class CustomerBusinessLogic : IDataAccessDependency
{
ICustomerDataAccess _dataAccess;
public CustomerBusinessLogic()
{
}
public string GetCustomerName(int id)
{
return _dataAccess.GetCustomerName(id);
}
public void SetDependency(ICustomerDataAccess customerDataAccess)
{
_dataAccess = customerDataAccess;
}
}
public class CustomerService
{
CustomerBusinessLogic _customerBL;
public CustomerService()
{
_customerBL = new CustomerBusinessLogic();
((IDataAccessDependency)_customerBL).SetDependency(new CustomerDataAccess());
}
public string GetCustomerName(int id) {
return _customerBL.GetCustomerName(id);
}
}
در مثال بالا، کلاس CustomerBusinessLogic رابط IDataAccessDependency را پیاده سازی میکند که شامل متد ()SetDependency است. بنابراین، کلاس injector CustomerService اکنون از این روش برای تزریق کلاس وابسته (CustomerDataAccess) به کلاس مشتری استفاده میکند.
نتیجه گیری
پس از خواندن این مقاله راهنما در مورد مفهوم Dependency Injection ، اکنون دیگر میدانید تزریق وابستگی چیست، انواع آن کدام است و چگونه میتواند قابلیت نگهداری کد را بهبود بخشد. همچنین با دیدن نمونه کدهای ارائه شده دیدی بهتر نسبت آن پیدا کردید. امیدواریم با این مطالب بتوایند روز به روز بر دانش برنامه نویسی خود بیفزاید و ما هم در تیم الکتروهایو در تلاش برای فراهم کردن مقالات مفید برای مخاطبان عزیز هستیم.