آموزش الگوی تزریق وابستگی در فلاتر Dependency Injection
تزریق وابستگی (Dependency Injection) یک الگوی طراحی نرمافزار است که به ما اجازه میدهد وابستگیهای یک شیء را از خارج تزریق کنیم به جای اینکه خود شیء آنها را ایجاد کند.
به عبارت دیگر، کلاسها وابستگیهای خود را از بیرون دریافت میکنند تا معکوسی در ترتیب تولید وابستگیها ایجاد شود.
برای آموزش بیشتر در زمینه معماری و الگوهای برنامه نویسی در دوره آموزش فلاتر به شکل کامل توضیح داده شده است.
تزریق وابستگی چیست؟
در تزریق وابستگی، اجزاء برای انجام وظایف خود به وابستگیهای خود نیاز دارند. این وابستگیها میتوانند به صورت کلاسها، اشیاء، یا سرویسهای دیگری باشند که اجزاء برای انجام کارهای خود نیاز دارند.
وجود یک Container یا مخزن تزریق وابستگی برای مدیریت و ارائه وابستگیها به اجزاء بسیار مهم است.
این مخزن عملیات تولید، مدیریت عمر و ارتباط بین اجزاء را بر عهده میگیرد.
این به کلاسها کمک میکند تا از جزئیات وابستگیهایشان جدا شده و تغییرات در وابستگیها به صورت مرکزی در Container اعمال شود.
مزایای تزریق وابستگی عبارتند از:
- جداگانهسازی مسئولیتها (Separation of Concerns): اجزاء مختلف برای وظایف مختلف جداگانه شده و بهبود یافته، این که کد به شکلی تمیزتر و قابل مدیریتتری تولید شود.
- آزمونپذیری بهتر (Better Testability): با تزریق وابستگیها به صورت مجازی (مثلاً با استفاده از متد ماکینگ یا جعبهابزار تست)، امکان تست واحد بهتری فراهم میشود.
- انعطافپذیری و تعویضپذیری (Flexibility and Reusability): امکان تغییر وابستگیها بدون تغییر کد اصلی کلاسها، و امکان استفاده مجدد از وابستگیها در جاهای دیگر را بهبود میبخشد.
- تفکیک مسئولیتهای کد (Decoupling): اجزاء اصلی از جزئیات پیچیده وابستگیها جدا میشوند که بهبود خوانایی و نگهداری کد را به همراه دارد.
در کل، تزریق وابستگی یک الگوی مهم در توسعه نرمافزار است که به کدنویسانان کمک میکند تا کدی قابل تست، تعویضپذیر و توسعهپذیر ایجاد کنند.
چرا از تزریق وابستگی Dependency injection باید استفاده کنیم
برای اینکه اهمیت تزریق وابستگی را بهتر درک کنید به کدهای زیر توجه کنید.
کدها با استفاده از زبان دارت پیاده سازی شده اند.
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 یک پکیج محبوب برای تزریق وابستگی در فلاتر است.
شما میتوانید بدون استفاده از پکیج خاصی هم آن را پیاده سازی کنید اما با کمک این پکیج کار برای ما بسیار راحت تر میشود.
get_it در واقع از الگوی Service locator استفاده میکند که با تزریق وابستگی تفاوت هایی دارد اما تقریبا میشود گفت که ۹۰% مشابه هستند به همین دلیل استفاده از آن مشکلی ایجاد نمیکند.
الگوی Serivce locator چیست؟
الگوی Service Locator یک الگوی طراحی نرمافزاری است که برای جلب و مدیریت وابستگیها و سرویسها در یک برنامه استفاده میشود.
در این الگو، یک مکان مرکزی (Service Locator) وجود دارد که به سیستم اجزاء و سرویسها را فراهم میکند. این مکان مرکزی مسئول تولید و ارائه وابستگیها به اجزاء مختلف برنامه است.
الگوی Service Locator به طور معمول با استفاده از یک رجیستر (Registry) که لیستی از وابستگیها و سرویسهای موجود در برنامه را نگهداری میکند، عمل میکند.
وقتی یک اجزاء نیاز به یک وابستگی دارد، به جای ایجاد آن به صورت مستقیم، از مکان مرکزی درخواست میکند تا مورد موردنیاز را از رجیستر دریافت کند.
تفاوت اصلی تزریق وابستگی و Service Locator در نحوه دسترسی به وابستگیها است:
در تزریق وابستگی، وابستگیها به صورت آرگومان به کلاس یا تابع تزریق میشوند. یعنی خود کلاس یا تابع کنترلی روی اینکه چه وابستگیهایی دریافت میکند، ندارد.
اما در Service Locator، کلاس یا تابع به صورت مستقیم از طریق یک شی Service Locator، وابستگی مورد نیاز خود را درخواست میکند.
پیاده سازی Dependency Injection در فلاتر
بعد از نصب پکیج 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
روش دیگر پیاده سازی وابستگی ها استفاده از الگوی Factory میباشد.
اگر نمیخواهید از الگوی Singleton استفاده کنید میتوانید Factory را جایگزین کنید.
void registerFactory<T>(FactoryFunc<T> func)
در این روش با هربار فراخوانی یک نمونه جدید از کلاس مورد نظر ایجاد میشود.
همچنین به یک متد از نوع factory هم نیاز دارید.
اگر قصد ارسال پارامتر هم داشته باشید میتوانید به صورت زیر عمل کنید.
void registerFactoryParamAsync<T,P1,P2>(FactoryFuncParamAsync<T,P1,P2> factoryfunc, {String instanceName});
امیدوارم که از این آموزش استفاده کافی و برده باشید.
دیدگاهتان را بنویسید