در باب تفاوت Dependency inejction و Service Locator

تزریق وابستگی به روایت تصویر =)))
تزریق وابستگی به روایت تصویر =)))

سه روش دسترسی به وابستگی‌ها

  1. تزریق وابستگی (Dependency injection)
  2. استفاده از Service Locator
  3. دسترسی مستقیم یا ساختن وابستگی (بعضی بحث‌ها پیرامون این مورد وجود داره که نمیخوان قبول کنن این مورد "وابستگی" محسوب میشه یا نه؛ ولی توی این بلاگ پست نمی‌گنجه.)



تزریق وابستگی

تزریق وابستگی به کلاس‌های کد ما این امکان رو میده که به وابستگی‌هاش دسترسی داشته باشه بدون اینکه درگیر روند ساخت اونا باشه. در هنگام اجرای برنامه، کلاس دیگه‌ای وظیفه‌ی مهیا کردن این وابستگی‌ها رو برای کلاس ما داره.

چهار رویکرد مختلف در پیاده‌سازی تزریق وابستگی

(البته که برخی از موارد زیر bad practice محسوب میشن)

۱.تزریق از طریق متد سازنده (Constructor Injection) : با این رویکرد وابستگی‌ها از طریق پارامتر‌های ورودی متد سازنده به کلاس تزریق میشن.

۲. تزریق از طریق متد Setter ـ(Setter Injection) : با این رویکرد برای هر وابستگی یک متد واسطه می‌سازیم و از طریق کلاس سازنده‌ی وابستگی‌ها اون وابستگی رو به کلاس تزریق می‌کنیم.

۳. تزریق در متغیر عمومی (Public variable Injection) : با این رویکرد باید متغیر وابستگی مورد نظر رو با دسترسی عمومی در کلاس نگه‌داریم سپس کلاس سازنده‌ی وابستگی‌ها، وابستگی مربوطه رو در متغیر آن تزریق می‌کنه.

۴. تزریق توسط Reflection ـ(Reflection Injection) : با این رویکرد با استفاده از Reflection به متغیر‌های داخلی کلاسی که می‌خواهیم به اون وابستگی رو تزریق کنیم دسترسی پیدا می‌کنیم و وابستگی‌ها رو در متغیر‌های مربوط به خودشون تزریق می‌کنیم.

خصوصت مشترک تمامی رویکردهای بالا این بود که یک کلاس خارجی مسئول تزریق وابستگی‌هاست.


الگوی Service Locator

وابستگی‌های مورد نیاز کلاس، توسط خود کلاس، از یک منبع خارجی تامین میشن. اگر درکش براتون سخته می‌تونید مثال OrderProcessor رو در زیر ببینید:

public class OrderProcessor : IOrderProcessor {
    public void Process(Order order){
        var validator = Locator.Resolve<IOrderValidator>();
        if (validator.Validate(order)) {
            var shipper = Locator.Resolve<IOrderShipper>();
            shipper.Ship(order);
        }
    }
}

همونطور که مشاهده می‌کنید توی مثال بالا ما داریم Validator و Shipper رو از منبعی به نام Locator درخواست می‌کنیم.
می‌تونید کد کلاس Locator رو ببینید:

public static class Locator {
    private static Dictionary<Type, Func<object>>
    services = new Dictionary<Type, Func<object>>();
    
    public static void Register<T>(Func<T> resolver) {
        Locator.services[typeof(T)] = () => resolver();
    }
 
    public static T Resolve<T>() {
        return (T)Locator.services[typeof(T)]();
    }
 
    public static void Reset() {
        Locator.services.Clear();
    }
}

کلاس Locator همونطور که می‌بینید یک Dictaionary (یا Map) داره که نوع وابستگی رو به عنوان کلید استفاده می‌کنه و تابع مربوط به ساخت اون وابستگی رو به عنوان مقدار نگه‌داری می‌کنه. ما هرموقع یک وابستگی رو بخوایم، توی منبعش می‌گرده و تابع مربوط به ساخت او وابستگی پیدا می‌کنه و اجراش می‌کنه و به ما یک نمونه جدید از اون وابستگی میده.


چرا Service Locator یک ضد الگو (Anti Pattern) شناخته میشه

یکی از ایراداتی که به Service Locator وارده، استفاده‌ی واسطه‌محور اونه. فرض کنید ما کسی نیستیم که کلاس Locator رو می‌نویسیم و از شخص ثالث (3rd party) این Locator رو تحویل گرفتیم؛ اونوقت اصلا اطلاعی نداریم که منبعی که به ما دادن می‌تونه وابستگی ما رو برطرف بکنه یا نه.
یکی دیگه از ایراداتی که به Service Locator وارده اینه که استفاده از این الگو باعث می‌شه وابستگی‌های کلاس هدف از دید استفاده کننده پنهون باشه، ما هیچوقت متوجه نمی‌شیم که کلاسی که قراره یک نمونه از اون بسازیم از چه وابستگی‌هایی داره استفاده می‌کنه چون که خودش داره از Locator اونا رو تامین می‌کنه.
یکی دیگه از ایراداتی که به Service Locator وارده اینه که اصل ISP از اصول SOLID در برنامه‌نویسی شی‌گرا رو نقض می‌کنه که می‌تونید توضیحات مربوط بهش رو توی این لینک مطالعه کنید.


کتابخونه‌ی Dagger 2 کجای داستانه؟

این نمونه‌ای از استفاده از کتابخونه‌ی Dagger2 برای تزریق وابستگیه:

class TargetClass {
    @Inject lateinit var dependency1 : Dependency1
    @Inject lateinit var dependency2 : Dependency2
    @Inject lateinit var dependency3 : Dependency3
    
    init {
        DaggerMyComponent.create().inject(this)
    }
}

همونطور که مشاهده می‌کنید فقط کافیه متغیر مربوط به وابستگی‌های مورد نیاز کلاس رو با نشونه‌ی Inject توضیح نویسی کنیم سپس با فراخوانی inject روی کلاس‌های سازنده‌ی مربوطه، وابستگی مورد نیاز رو به کلاسمون تزریق کنیم.
این روش، نوعی پیاده‌سازی از رویکرد سوم پیاده‌سازی تزریق وابستگی محسوب میشه ولی از اونجایی که "خود کلاس" داره درخواست تزریق رو میده، معتقدن که استفاده از کتابخونه‌ی Dagger2 اگر ۹۵٪ تزریق وابستگی محسوب بشه ۵٪ هم داره استفاده از الگوی Service Locator محسوب میشه.

مشکل اندروید چیه با تزریق وابستگی؟

مشکل اندروید اینه که ساخت بعضی کلاس‌های مورد نیاز اندروید (مثل اکتیویتی و فرگمنت) توسط خود سیستم‌عامل انجام میشه و ما اصلا نمیتونیم به صورت دستی اونا رو استفاده کنیم واسه همین به مشکل می‌خوریم
اکتیویتی‌ها توی اندروید معمولا با استفاده از دستور زیر توسط خود سیستم‌عامل ساخته میشن:

startActivity(Intent(this, thatActivity::class.java))

درسته که فرگمنت‌ها در ابتدا توسط خودمون ساخته می‌شن اما اگر Configuration Change اتفاق بیفته خود سیستم‌عامل فرگمنت‌ها رو دوباره می‌سازه که در این صورت بازم وابستگی‌ها رو از دست می‌دیم.

راه حلش چیه؟

راه حلش اینه که ما از توی خود کلاس درخواست تزریق وابستگی‌ها رو بدیم. اینطور می‌تونیم مشکل ساخت کلاس‌ها توسط سیستم‌عامل رو حل کنیم. چیزی مشابه عکس زیر :

این مشکل دقیقا با کتابخونه‌ی dagger2 حل شده و می‌تونید از این کتابخونه برای پیاده‌سازی و استفاده از Dependency Injection توی برنامه‌هاتون استفاده کنید.


منابع

Service Locator is an Anti-Pattern
Dependency Injection or Service Locator | by Elye
Dependency Injection and Service Locator | by Lam Pham
Why Android Apps uses Dagger 2?| by Elye
Service Locator violates SOLID


ممنونم از این که این پست رو مطالعه کردید
امیدوارم که براتون مفید باشه و اگر بود لطفا با دوستاتون هم به اشتراک بگذارید.

نوشته شده با ❤️ توسط کوچیکتون حمیدرضا شجراوی =)