החדרת תלות (DI) היא טכניקה נפוצה בתכנות, שמתאימות לפיתוח של Android. אתם פועלים לפי עקרונות ה-DI, ליצירת ארכיטקטורה טובה של אפליקציות.
יישום של הזרקת תלות מספק את היתרונות הבאים:
- שימוש חוזר בקוד
- ארגון מחדש בקלות
- קלות הבדיקה
יסודות של החדרת תלות
לפני הנושא של החדרת תלות ב-Android באופן ספציפי, הדף הזה מספק סקירה כללית יותר על האופן שבו פועלת החדרת תלות.
מהי החדרת תלות?
בכיתות בדרך כלל צריך להפנות לכיתות אחרות. לדוגמה, כיתה ב-Car
יכול להיות שצריך הפניה למחלקה Engine
. הכיתות הנדרשות האלה נקראות
dependenions, ובדוגמה הזו, המחלקה Car
תלויה
באמצעות מופע של המחלקה Engine
.
יש שלוש דרכים שבהן מחלקה יכולה לקבל אובייקט שהיא צריכה:
- המחלקה בונה את התלות הדרושה. בדוגמה שלמעלה,
Car
ייצור ויאתחל מופע משלו שלEngine
. - לוקחים אותו ממקום אחר. בחלק מממשקי ה-API של Android, כמו
Context
מקבלים ו-getSystemService()
, מה צריך לעשות? בדרך הזו. - יש לספק אותו כפרמטר. האפליקציה יכולה לספק את הפרטים הבאים:
של יחסי התלות כשהמחלקה נבנית או מעבירה אותם לפונקציות
שזקוקים לכל תלות. בדוגמה שלמעלה, השדה
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 ולכן ניתן לבדוק אותם בזמן יצירת האובייקט או בזמן הקומפילציה במקום להיות מוסתרים כפרטי הטמעה.
קלות הבדיקה: כיתה לא מנהלת את יחסי התלות, ולכן לבדוק אותו, אפשר להעביר באפליקציות שונות כדי לבדוק במקרים השונים.
כדי להבין את היתרונות של הזרקת תלות, כדאי לנסות באופן ידני באפליקציה, כפי שמוצג בהחדרת תלות ידנית.
מקורות מידע נוספים
למידע נוסף על החדרת תלות, כדאי לעיין במקורות המידע הנוספים הבאים.