تزریق وابستگی (Dependency Injection) یک الگوی طراحی نرمافزار است که به ما اجازه میدهد وابستگیهای یک شیء را از خارج تزریق کنیم به جای اینکه خود شیء آنها را ایجاد کند.
به عبارت دیگر، کلاسها وابستگیهای خود را از بیرون دریافت میکنند تا معکوسی در ترتیب تولید وابستگیها ایجاد شود.
برای آموزش بیشتر در زمینه معماری و الگوهای برنامه نویسی در دوره آموزش فلاتر به شکل کامل توضیح داده شده است.
در تزریق وابستگی، اجزاء برای انجام وظایف خود به وابستگیهای خود نیاز دارند. این وابستگیها میتوانند به صورت کلاسها، اشیاء، یا سرویسهای دیگری باشند که اجزاء برای انجام کارهای خود نیاز دارند.
وجود یک Container یا مخزن تزریق وابستگی برای مدیریت و ارائه وابستگیها به اجزاء بسیار مهم است.
این مخزن عملیات تولید، مدیریت عمر و ارتباط بین اجزاء را بر عهده میگیرد.
این به کلاسها کمک میکند تا از جزئیات وابستگیهایشان جدا شده و تغییرات در وابستگیها به صورت مرکزی در Container اعمال شود.
مزایای تزریق وابستگی عبارتند از:
در کل، تزریق وابستگی یک الگوی مهم در توسعه نرمافزار است که به کدنویسانان کمک میکند تا کدی قابل تست، تعویضپذیر و توسعهپذیر ایجاد کنند.
برای اینکه اهمیت تزریق وابستگی را بهتر درک کنید به کدهای زیر توجه کنید.
کدها با استفاده از زبان دارت پیاده سازی شده اند.
class University{
String? university_name;
Student? student;
University(this.university_name,String name){
student = Student(null, name);
}
void ShowInfo(){
print("Hi ${student!.person!.name} - number: ${student!.student_number} - university: $university_name");
}
}
class Student{
String? student_number;
Person? person;
Student(this.student_number,String name){
person = Person(name);
}
void ShowInfo(){
print("Hi ${person!.name} - number: $student_number");
}
}
class Person{
String? name;
Person(this.name,);
void ShowInfo(){
print("Hi $name ");
}
}
در این کد کلاس Student به کلاس Person وابسته است.
همچنین کلاس University نیز دارای وابستگی قوی به کلاس Student میباشد.
اگر کلاس Person را تغییر دهیم به گونه ای که متد سازنده آن تغییر کند کلاس Student نیز باید تغییر کند که به تبع آن کلاس University هم باید تغییر کند.
پس میبینید که این وابستگی ها چه مشکلاتی را ایجاد میکنند.
در پروژه های واقعی که حجم کلاس ها بسیار بیشتر و پیچیده تر هستند اضافه کردن یک ویژگی به کلاسی ممکن است باعث تغییر ساختار کلی پروژه شود.
با استفاده از تزریق وابستگی این وابستگی ها را سعی میکنیم تا کمتر کنیم.
get_it یک پکیج محبوب برای تزریق وابستگی در فلاتر است.
شما میتوانید بدون استفاده از پکیج خاصی هم آن را پیاده سازی کنید اما با کمک این پکیج کار برای ما بسیار راحت تر میشود.
get_it در واقع از الگوی Service locator استفاده میکند که با تزریق وابستگی تفاوت هایی دارد اما تقریبا میشود گفت که 90% مشابه هستند به همین دلیل استفاده از آن مشکلی ایجاد نمیکند.
الگوی Service Locator یک الگوی طراحی نرمافزاری است که برای جلب و مدیریت وابستگیها و سرویسها در یک برنامه استفاده میشود.
در این الگو، یک مکان مرکزی (Service Locator) وجود دارد که به سیستم اجزاء و سرویسها را فراهم میکند. این مکان مرکزی مسئول تولید و ارائه وابستگیها به اجزاء مختلف برنامه است.
الگوی Service Locator به طور معمول با استفاده از یک رجیستر (Registry) که لیستی از وابستگیها و سرویسهای موجود در برنامه را نگهداری میکند، عمل میکند.
وقتی یک اجزاء نیاز به یک وابستگی دارد، به جای ایجاد آن به صورت مستقیم، از مکان مرکزی درخواست میکند تا مورد موردنیاز را از رجیستر دریافت کند.
تفاوت اصلی تزریق وابستگی و Service Locator در نحوه دسترسی به وابستگیها است:
در تزریق وابستگی، وابستگیها به صورت آرگومان به کلاس یا تابع تزریق میشوند. یعنی خود کلاس یا تابع کنترلی روی اینکه چه وابستگیهایی دریافت میکند، ندارد.
اما در Service Locator، کلاس یا تابع به صورت مستقیم از طریق یک شی Service Locator، وابستگی مورد نیاز خود را درخواست میکند.
بعد از نصب پکیج get_it کار ما با ساخت یک فایل مرکزی برای مدیریت وابستگی ها شروع میشود.
فرض کنید مثال اولیه را بخواهیم بصورت زیر تغییر دهیم.
class Person{
String? name;
Person? parent;
Person(this.name,this.parent);
void ShowInfo(){
print("Hi $name - parent name: ${parent!.name}");
}
}
class Parent{
String? name;
Parent(this.name);
}
در این شرایط که کلاس Person تغییر میکند تمام کلاس های وابسته به آن نیز باید تغییر کنند.
اما میخواهیم از این مشکل جلوگیری کنیم.
یک فایل جدید به نام locator ایجاد میکنیم و کد های زیر را در آن قرار میدهیم.
final GetIt sl = GetIt.instance;
void Setup(){
sl.registerSingleton<Parent>(Parent('Ali'));
}
در این کد کلاس Parent را به صورت SingleTon تعریف کرده ایم و آن را به نمونه از کلاس GetIt متصل کرده ایم.
حالا در هر قسمتی از برنامه که به نمونه از این کلاس نیاز داشتیم کافیست از GetIt استفاده کنیم برای دریافت آن.
کدهای کلاس Person را به شکل زیر تغییر میدهیم.
class Person{
String? name;
Person(this.name,);
void ShowInfo(){
Parent parent = sl<Parent>();
print("Hi $name - parent name: ${parent.name}");
}
}
میبینید کلاس Person بدون اینکه تغییر در فیلدهای آن بدهیم یک نمونه از کلاس Parent داخل خود دریافت میکند.
در حال حاضر سایر کلاس های وبسته به Person بدون هیچ مشکلی به کار خود ادامه میدهند.
در متد main حتما باید متد Setup فراخوانی شود.
void main(){
Setup();
Person person = Person("Hesam");
person.ShowInfo();
}
در حال حاضر شما موفق شدید که وابستگی مورد نظر و با استفاده از پکیج فلاتر get_it مدیریت کنید.
روش دیگر پیاده سازی وابستگی ها استفاده از الگوی Factory میباشد.
اگر نمیخواهید از الگوی Singleton استفاده کنید میتوانید Factory را جایگزین کنید.
void registerFactory<T>(FactoryFunc<T> func)
در این روش با هربار فراخوانی یک نمونه جدید از کلاس مورد نظر ایجاد میشود.
همچنین به یک متد از نوع factory هم نیاز دارید.
اگر قصد ارسال پارامتر هم داشته باشید میتوانید به صورت زیر عمل کنید.
void registerFactoryParamAsync<T,P1,P2>(FactoryFuncParamAsync<T,P1,P2> factoryfunc, {String instanceName});
امیدوارم که از این آموزش استفاده کافی و برده باشید.