החדרת תלות ב-Android

החדרת תלות (DI) היא טכניקה נפוצה בתכנות, שמתאימות לפיתוח של Android. אתם פועלים לפי עקרונות ה-DI, ליצירת ארכיטקטורה טובה של אפליקציות.

יישום של הזרקת תלות מספק את היתרונות הבאים:

  • שימוש חוזר בקוד
  • ארגון מחדש בקלות
  • קלות הבדיקה

יסודות של החדרת תלות

לפני הנושא של החדרת תלות ב-Android באופן ספציפי, הדף הזה מספק סקירה כללית יותר על האופן שבו פועלת החדרת תלות.

מהי החדרת תלות?

בכיתות בדרך כלל צריך להפנות לכיתות אחרות. לדוגמה, כיתה ב-Car יכול להיות שצריך הפניה למחלקה Engine. הכיתות הנדרשות האלה נקראות dependenions, ובדוגמה הזו, המחלקה Car תלויה באמצעות מופע של המחלקה Engine.

יש שלוש דרכים שבהן מחלקה יכולה לקבל אובייקט שהיא צריכה:

  1. המחלקה בונה את התלות הדרושה. בדוגמה שלמעלה, Car ייצור ויאתחל מופע משלו של Engine.
  2. לוקחים אותו ממקום אחר. בחלק מממשקי ה-API של Android, כמו Context מקבלים ו-getSystemService(), מה צריך לעשות? בדרך הזו.
  3. יש לספק אותו כפרמטר. האפליקציה יכולה לספק את הפרטים הבאים: של יחסי התלות כשהמחלקה נבנית או מעבירה אותם לפונקציות שזקוקים לכל תלות. בדוגמה שלמעלה, השדה Car יקבל את Engine כפרמטר.

האפשרות השלישית היא החדרת תלות! בגישה הזאת את יחסי התלות של כיתה ומספקים אותם במקום שהכיתה להשיג אותן בעצמו.

נראה דוגמה. ללא החדרת תלות, שמייצגת Car יוצרת תלות Engine משלה בקוד נראית כך:

Kotlin

class Car {

    private val engine = Engine()

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

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

Java

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 במקום להשתמש שוב באותו Car למנועים מסוג Gas וגם Electric.

  • בגלל התלות הקשה ב-Engine, קשה יותר לבצע את הבדיקות. Car משתמש ב: את המופע האמיתי של Engine, וכך מונע ממך להשתמש כפול בדיקה כדי לשנות את Engine עבור מקרי בדיקה שונים.

איך הקוד נראה עם החדרת תלות? במקום כל מופע של Car בונה אובייקט Engine משלו באתחול, הוא מקבל האובייקט Engine כפרמטר ב-constructor שלו:

Kotlin

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

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

Java

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 ולהגדיר אותו לבדיקות שונות.

יש שתי דרכים עיקריות לבצע החדרת תלות ב-Android:

  • החדרת בונה. זו הדרך שמתוארת למעלה. מעבירים את יחסי התלות של מחלקה לבונה שלה.

  • החדרת שדה (או הזרקת הגדרה). כיתות מסוימות של Android framework כמו פעילויות ומקטעים, הם נוצרים על ידי המערכת, כך ש-constructor והזרקה אינה אפשרית. יחסי התלות נוצרים באמצעות החדרת שדה אחרי יצירת הכיתה. הקוד ייראה כך:

Kotlin

class Car {
    lateinit var engine: Engine

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

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

Java

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 היא ספריית הזרקת תלות פופולרית ב-Java, Kotlin ו-Android שמופעלים על ידי Google. Dagger מאפשר שימוש ב-DI באפליקציה שלכם על ידי יצירה וניהול של תרשים יחסי התלות. הוא מספקת יחסי תלות סטטיים לגמרי בזמן הידור (compile) של פתרונות שמבוססים על השתקפות וביצועים, Guice.

חלופות להזרקת תלות

חלופה להחדרת תלות היא שימוש service locator. גם דפוס העיצוב של מאתר השירות משפרת את ההפרדה בין סיווגים מיחסי תלות קונקרטיים. אתם יוצרים כיתה שנקרא מאתר השירותים, שיוצר ומאחסן יחסי תלות מספקת את יחסי התלות האלה לפי דרישה.

Kotlin

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()
}

Java

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 באפליקציה ל-Android

Hilt הוא ההמלצה של Jetpack להזרקת תלות ב-Android. Hilt מגדיר דרך סטנדרטית DI באפליקציה באמצעות קונטיינרים לכל מחלקה של Android פרויקט וניהול מחזורי החיים שלהם באופן אוטומטי.

Hilt מבוסס על ספריית DI הפופולרית Dagger כדי להפיק תועלת לבצע הידור של נכונות הזמן, ביצועים בסביבת זמן ריצה, מדרגיות ויכולת התאמה ל-Android Studio. ו-Dagger.

מידע נוסף על Hilt מופיע הזרקת תלות באמצעות Hilt

סיכום

החדרת תלות מספקת לאפליקציה את היתרונות הבאים:

  • שימוש חוזר במחלקות והפרדת יחסי תלות: קל יותר להחליף של תלות. השימוש החוזר בקוד השתפר בגלל ההיפוך בשליטה שלהם, והמחלקות לא שולטות יותר באופן שבו יחסי התלות שלהם נוצרים. אבל הם פועלים עם כל הגדרה אישית.

  • קלות הארגון מחדש: יחסי התלות הופכים לחלק שאפשר לאמת ב-API ולכן ניתן לבדוק אותם בזמן יצירת האובייקט או בזמן הקומפילציה במקום להיות מוסתרים כפרטי הטמעה.

  • קלות הבדיקה: כיתה לא מנהלת את יחסי התלות, ולכן לבדוק אותו, אפשר להעביר באפליקציות שונות כדי לבדוק במקרים השונים.

כדי להבין את היתרונות של הזרקת תלות, כדאי לנסות באופן ידני באפליקציה, כפי שמוצג בהחדרת תלות ידנית.

מקורות מידע נוספים

למידע נוסף על החדרת תלות, כדאי לעיין במקורות המידע הנוספים הבאים.

דוגמיות