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

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

اجرای تزریق وابستگی مزایای زیر را برای شما فراهم می کند:

  • قابلیت استفاده مجدد از کد
  • سهولت بازسازی مجدد
  • سهولت تست

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

قبل از پرداختن به تزریق وابستگی در اندروید، این صفحه نمای کلی تری از نحوه عملکرد تزریق وابستگی ارائه می دهد.

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

کلاس ها اغلب نیاز به ارجاع به کلاس های دیگر دارند. برای مثال، یک کلاس Car ممکن است نیاز به ارجاع به کلاس Engine داشته باشد. این کلاس‌های مورد نیاز، وابستگی نامیده می‌شوند، و در این مثال، کلاس Car به داشتن نمونه‌ای از کلاس Engine برای اجرا وابسته است.

سه راه برای یک کلاس برای به دست آوردن یک شی مورد نیاز وجود دارد:

  1. کلاس وابستگی مورد نیاز خود را می سازد. در مثال بالا، Car نمونه‌ای از Engine را ایجاد و مقداردهی اولیه می‌کند.
  2. آن را از جای دیگری بگیرید. برخی از API های اندروید، مانند Context getters و getSystemService() به این روش کار می کنند.
  3. آن را به عنوان یک پارامتر ارائه کنید. برنامه می‌تواند این وابستگی‌ها را زمانی که کلاس ساخته می‌شود ارائه کند یا آن‌ها را به توابعی که به هر وابستگی نیاز دارند منتقل کند. در مثال بالا، سازنده Car Engine به عنوان پارامتر دریافت می کند.

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

در اینجا یک مثال است. بدون تزریق وابستگی، نمایش Car که وابستگی Engine خود را در کد ایجاد می‌کند به این صورت است:

کاتلین

class Car {

    private val engine = Engine()

    fun start() {
        engine.start()
    }
}

fun main(args: Array) {
    val car = Car()
    car.start()
}

جاوا

class Car {

    private Engine engine = new Engine();

    public void start() {
        engine.start();
    }
}


class MyApp {
    public static void main(String[] args) {
        Car car = new Car();
        car.start();
    }
}
کلاس خودرو بدون تزریق وابستگی

این نمونه ای از تزریق وابستگی نیست زیرا کلاس Car در حال ساخت Engine خود است. این می تواند مشکل ساز باشد زیرا:

  • Car و Engine کاملاً جفت شده‌اند - نمونه‌ای از Car از یک نوع Engine استفاده می‌کند و هیچ کلاس فرعی یا پیاده‌سازی جایگزینی نمی‌تواند به راحتی استفاده شود. اگر قرار بود Car Engine خود را بسازد، شما باید به جای استفاده مجدد از همان Car برای موتورهای نوع Gas و Electric ، دو نوع Car ایجاد کنید.

  • وابستگی سخت به Engine تست را دشوارتر می کند. Car از یک نمونه واقعی Engine استفاده می کند، بنابراین شما را از استفاده از یک تست دوبل برای تغییر Engine برای موارد مختلف تست جلوگیری می کند.

کد با تزریق وابستگی چگونه به نظر می رسد؟ به جای اینکه هر نمونه از Car شی Engine خود را در مقدار دهی اولیه بسازد، یک شی Engine را به عنوان پارامتر در سازنده خود دریافت می کند:

کاتلین

class Car(private val engine: Engine) {
    fun start() {
        engine.start()
    }
}

fun main(args: Array) {
    val engine = Engine()
    val car = Car(engine)
    car.start()
}

جاوا

class Car {

    private final Engine engine;

    public Car(Engine engine) {
        this.engine = engine;
    }

    public void start() {
        engine.start();
    }
}


class MyApp {
    public static void main(String[] args) {
        Engine engine = new Engine();
        Car car = new Car(engine);
        car.start();
    }
}
کلاس خودرو با استفاده از تزریق وابستگی

عملکرد main از Car استفاده می کند. از آنجایی که Car به Engine بستگی دارد، برنامه یک نمونه از Engine ایجاد می کند و سپس از آن برای ساختن یک نمونه از Car استفاده می کند. مزایای این رویکرد مبتنی بر DI عبارتند از:

  • قابلیت استفاده مجدد Car شما می توانید در پیاده سازی های مختلف از Engine به Car عبور دهید. به عنوان مثال، ممکن است یک زیر کلاس جدید از Engine به نام ElectricEngine تعریف کنید که می خواهید Car از آن استفاده کند. اگر از DI استفاده می‌کنید، تنها کاری که باید انجام دهید این است که نمونه‌ای از زیرکلاس ElectricEngine به‌روزرسانی شده را پاس کنید، و Car همچنان بدون هیچ تغییر دیگری کار می‌کند.

  • تست آسان Car برای آزمایش سناریوهای مختلف خود می توانید در دو آزمون قبول شوید. به عنوان مثال، ممکن است یک تست دوبل از Engine به نام FakeEngine ایجاد کنید و آن را برای تست های مختلف پیکربندی کنید.

دو روش اصلی برای تزریق وابستگی در اندروید وجود دارد:

  • تزریق سازنده . این راهی است که در بالا توضیح داده شد. شما وابستگی های یک کلاس را به سازنده آن منتقل می کنید.

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

کاتلین

class Car {
    lateinit var engine: Engine

    fun start() {
        engine.start()
    }
}

fun main(args: Array) {
    val car = Car()
    car.engine = Engine()
    car.start()
}

جاوا

class Car {

    private Engine engine;

    public void setEngine(Engine engine) {
        this.engine = engine;
    }

    public void start() {
        engine.start();
    }
}

class MyApp {
    public static void main(String[] args) {
        Car car = new Car();
        car.setEngine(new Engine());
        car.start();
    }
}

تزریق وابستگی خودکار

در مثال قبلی، شما بدون اتکا به کتابخانه، وابستگی های کلاس های مختلف را خودتان ایجاد، ارائه و مدیریت کردید. به این عمل تزریق وابستگی با دست یا تزریق وابستگی دستی می گویند. در مثال Car ، تنها یک وابستگی وجود داشت، اما وابستگی‌ها و کلاس‌های بیشتر می‌تواند تزریق دستی وابستگی‌ها را خسته‌کننده‌تر کند. تزریق وابستگی دستی نیز چندین مشکل را به همراه دارد:

  • برای برنامه های بزرگ، گرفتن همه وابستگی ها و اتصال صحیح آنها می تواند به مقدار زیادی کد دیگ بخار نیاز داشته باشد. در یک معماری چند لایه، برای ایجاد یک شی برای لایه بالایی، باید تمام وابستگی های لایه های زیر آن را فراهم کنید. به عنوان مثال ملموس، برای ساخت یک ماشین واقعی ممکن است به موتور، گیربکس، شاسی و سایر قطعات نیاز داشته باشید. و یک موتور به نوبه خود نیاز به سیلندر و شمع دارد.

  • وقتی نمی‌توانید وابستگی‌ها را قبل از ارسال آن‌ها بسازید - برای مثال هنگام استفاده از مقداردهی اولیه تنبل یا محدوده‌بندی اشیاء در جریان برنامه‌تان - باید یک ظرف سفارشی (یا نمودار وابستگی‌ها) بنویسید و نگهداری کنید که طول عمر شما را مدیریت کند. وابستگی در حافظه

کتابخانه هایی وجود دارند که این مشکل را با خودکارسازی فرآیند ایجاد و ارائه وابستگی ها حل می کنند. آنها در دو دسته قرار می گیرند:

  • راه حل های مبتنی بر بازتاب که وابستگی ها را در زمان اجرا به هم متصل می کنند.

  • راه حل های ایستا که کدی را برای اتصال وابستگی ها در زمان کامپایل تولید می کنند.

Dagger یک کتابخانه تزریق وابستگی محبوب برای جاوا، کاتلین و اندروید است که توسط گوگل نگهداری می شود. Dagger با ایجاد و مدیریت نمودار وابستگی ها برای شما، استفاده از DI را در برنامه شما تسهیل می کند. این وابستگی های کاملا ایستا و زمان کامپایل را فراهم می کند که به بسیاری از مسائل توسعه و عملکرد راه حل های مبتنی بر بازتاب مانند Guice می پردازد.

جایگزین های تزریق وابستگی

جایگزینی برای تزریق وابستگی استفاده از سرویس یاب است. الگوی طراحی مکان یاب خدمات نیز جداسازی طبقات از وابستگی های بتن را بهبود می بخشد. شما یک کلاس به نام سرویس یاب ایجاد می‌کنید که وابستگی‌ها را ایجاد و ذخیره می‌کند و سپس آن وابستگی‌ها را در صورت تقاضا فراهم می‌کند.

کاتلین

object ServiceLocator {
    fun getEngine(): Engine = Engine()
}

class Car {
    private val engine = ServiceLocator.getEngine()

    fun start() {
        engine.start()
    }
}

fun main(args: Array) {
    val car = Car()
    car.start()
}

جاوا

class ServiceLocator {

    private static ServiceLocator instance = null;

    private ServiceLocator() {}

    public static ServiceLocator getInstance() {
        if (instance == null) {
            synchronized(ServiceLocator.class) {
                instance = new ServiceLocator();
            }
        }
        return instance;
    }

    public Engine getEngine() {
        return new Engine();
    }
}

class Car {

    private Engine engine = ServiceLocator.getInstance().getEngine();

    public void start() {
        engine.start();
    }
}

class MyApp {
    public static void main(String[] args) {
        Car car = new Car();
        car.start();
    }
}

الگوی مکان یاب سرویس با تزریق وابستگی در نحوه مصرف عناصر متفاوت است. با الگوی یاب سرویس، کلاس ها کنترل دارند و درخواست می کنند که اشیا تزریق شوند. با تزریق وابستگی، برنامه کنترل دارد و به طور فعال اشیاء مورد نیاز را تزریق می کند.

در مقایسه با تزریق وابستگی:

  • مجموعه وابستگی‌های مورد نیاز یک سرویس یاب، آزمایش کد را سخت‌تر می‌کند، زیرا همه آزمایش‌ها باید با یک سرویس یاب جهانی یکسان تعامل داشته باشند.

  • وابستگی ها در پیاده سازی کلاس کدگذاری می شوند، نه در سطح API. در نتیجه، سخت تر است که بفهمیم یک کلاس از بیرون به چه چیزی نیاز دارد. در نتیجه، تغییرات در Car یا وابستگی‌های موجود در مکان یاب سرویس ممکن است منجر به زمان اجرا یا خطای آزمایشی شود و باعث از کار افتادن مراجع شود.

  • اگر بخواهید به چیزی غیر از طول عمر کل برنامه دسترسی داشته باشید، مدیریت طول عمر اشیا دشوارتر است.

از Hilt در برنامه اندروید خود استفاده کنید

Hilt کتابخانه پیشنهادی Jetpack برای تزریق وابستگی در اندروید است. Hilt با ارائه کانتینرهایی برای هر کلاس Android در پروژه شما و مدیریت چرخه عمر آنها به طور خودکار برای شما، یک روش استاندارد برای انجام DI در برنامه شما تعریف می کند.

Hilt بر روی کتابخانه محبوب DI Dagger ساخته شده است تا از صحت زمان کامپایل، عملکرد زمان اجرا، مقیاس پذیری و پشتیبانی از Android Studio که Dagger ارائه می کند بهره مند شود.

برای کسب اطلاعات بیشتر در مورد Hilt به تزریق وابستگی با هیلت مراجعه کنید.

نتیجه گیری

تزریق وابستگی مزایای زیر را به برنامه شما ارائه می دهد:

  • قابلیت استفاده مجدد از کلاس ها و جداسازی وابستگی ها: تعویض پیاده سازی یک وابستگی آسان تر است. استفاده مجدد از کد به دلیل وارونگی کنترل بهبود یافته است، و کلاس‌ها دیگر نحوه ایجاد وابستگی‌های خود را کنترل نمی‌کنند، بلکه با هر پیکربندی کار می‌کنند.

  • سهولت بازآفرینی: وابستگی ها به بخشی قابل تأیید از سطح API تبدیل می شوند، بنابراین می توان آنها را در زمان ایجاد شی یا در زمان کامپایل بررسی کرد نه اینکه به عنوان جزئیات پیاده سازی پنهان شوند.

  • سهولت تست: یک کلاس وابستگی های خود را مدیریت نمی کند، بنابراین زمانی که آن را آزمایش می کنید، می توانید پیاده سازی های مختلف را پاس کنید تا همه موارد مختلف خود را آزمایش کنید.

برای درک کامل مزایای تزریق وابستگی، باید آن را به صورت دستی در برنامه خود امتحان کنید، همانطور که در تزریق وابستگی دستی نشان داده شده است.

منابع اضافی

برای کسب اطلاعات بیشتر در مورد تزریق وابستگی، به منابع اضافی زیر مراجعه کنید.

نمونه ها