-->

יום שבת, 27 בספטמבר 2014

להבין Dependency Injection [מעודכן]

עדכון: ספטמבר 2014 - הפוסט חודש כליל. הפוסט המקורי פורסם בספטמבר 2012.

לשימוש ב Dependency Injection (בקיצור: DI) יש שני מניעים עיקריים:
  • הקפדה על ה Single Responsibility Principle[א] (בקיצור: SRP).
  • ביצוע בדיקות יחידה / בדיקות אינטגרציה.
לעתים, השימוש ב DI הופך מוטמע כ"כ עמוק בפרויקט / בתהליכים (״התשתית שלנו היא Spring״) - עד שניתן לשכוח מהיכן הכל התחיל. כאשר מקפידים מאוד על שימוש ב DI, אך מחלקות בפרויקט עושות מטלות שונות - זה נראה סוג של "פספוס": זה כמו להאכיל את הילדים בקינואה וברוקולי בצהריים, אבל צ'יפס וקרמבו בשאר הארוחות. מותר... אבל לא מומלץ.

DI הוא גם פתרון ממשי ומעשי לבעיות שצצות בביצוע בדיקות יחידה, ואפילו יותר: בביצוע בדיקות אינטגרציה. אני עבדתי עם קבוצות פיתוח שהוסיפו DI בכדי לבצע בדיקות יחידה, וללא קשר ל SRP (ל SRP היה לנו כלי אחר).

כיום, נראה שביצוע בדיקות יחידה/אינטגרציה הוא יותר נפוץ מהקפדה מלאה על SRP. רוב הארגונים עושים היום בדיקות יחידה, אבל חלק קטן מהם (מניסיוני האישי) באמת מקפיד באמת על SRP. ויש עוד אחוז מסוים של ארגונים שמשתמשים בכלי DI - "כדי שיעשה סדר", אך ללא יכולת להסביר מהו בדיוק הסדר הזה שהם מצפים לו וכיצד הוא יקרה. לצפות ל"פחות באגים" בעקבות שימוש בכלי DI זה נחמד, אבל ניתן באותה מידה לצפות לכזו תוצאה משימוש ב Log4J... ללא שימוש נכון ויעיל - הציפיה היא תקוות שווא.


בפוסט זה אני מנסה גישה חדשה: פרסמתי את דוגמאות הקוד (העובדות) מהפוסט בגיטהאב: https://github.com/baronlior/di-post
בואו נראה איך זה עובד...







DI בהיבט של SRP


בואו נתבונן על הקוד הבא:

הממשק

ספק השירות

צרכן השירות


הנה השקענו, בהגדרת Interface (ע"פ עקרון ה Program to an interface), בהפרדה בין נותן שירות לצרכן השירות. הפרדה זו ניתן לקטלג כ"ניהול תלויות" / "Low Coupling" - ערכים בסיסיים בהנדסת תוכנה.

צרכן השירות משתמש בשירות דרך ממשק, וכך הוא מוגבל לשימוש בממשק המצומצם (ובשאיפה: היציב) שהוגדר.
אמהמה: מישהו, מישהו צריך ליצור מופע (instance) של האובייקט Michelangelo ולתת רפרס שלו לצרכן השירות, והמישהו הזה - הוא צרכן השירות עצמו (!).

אמנם המשתנה המקומי artist הוא מסוג הממשק Artist - אך השימוש ב new מחייב שיהיה import ותלות למחלקה הקונקרטית. זרעי הפרת-התלויות - נזרעו.

מדוע הפסימיות? המתכנת ישתמש בממשק מבלי קשר ל imports הזמינים...

אמשיל את העניין למשהו יותר יומיומי:
דמיינו שאתם מטיילים ונתקלים בגדר עץ פשוטה ולא גבוהה. גדר שכזו היא כמעט אף פעם איננו מכשול שקשה לדלג מעליו - אבל היא עדיין מכשול פסיכולוגי אפקטיבי.
אם חלק מהגדר חסר (="פרצה") אזי כוחו של המכשול הפסיכולוגי פוחת, וסביר יותר שתרשו לעצמכם לעבור את הגדר. להזכיר: הגדר מעולם לא הייתה מחסום פיסי מאתגר.

באופן דומה חשבו על המתכנת: הממשק הוא כמו גדר - זהו מחסום פיסי לא מאתגר. מה הבעיה ללכת לקוד של ה Interface ולשנות אותו? או לשנות את ההגדרה בקוד של המשתנה מטיפוס Artist (הממשק) לטיפוס Michelangelo (המחלקה הקונקרטית)? ממש כלום.

אבל יש פה מחסום פסיכולוגי: המתכנת יודע ש Interface מייצג קודים חברתיים מסוימים - לא עוקפים ממשק סתם כך. אם יש לו גישה זמינה למחלקה הקונקרטית (import) זוהי פרצה בגדר: עדיין הקוד החברתי קיים, אך קל יותר לבטל אותו / להתעלם ממנו. ויש את ה Auto-complete המסיתן הזה, שמציע למתכנת להפר, "רק לרגע", את הכללים.

מלבד החשש לטשטוש הגבולות שהוגדרו, יש פה עניין של "קוד מיותר".
יצירת הקשרים בין המלקות הוא לא חלק מה "Single Responsibility" של המחלקה - ופשוט עדיף שלא יהיה שם. עדיף ליצור מחלקה מיוחדת (למשל: Factory) שזהו ייעודה בחיים.

איך אפשר לעשות זאת?
- פשוט "נעיף" את ה import של המחלקה הקונקרטית מצרכן השירות ונוסיף מחלקה שהכרת המחלקות הקונקרטיות הוא ייעודה. Repository בו מחלקות יכולות להירשם, ולמצוא אחת את השנייה ע"פ "סימנים" מסוימים (שם, שם התפקיד, ממשק, וכו'). נקרא לו Service Locator:


דוגמא לשימוש ב Service Locator
הנה, כרגע אין לנו תלויות במימוש הקונקרטי - סגרנו את "הפרצה בגדר"!

עם השנים התפתחו ספריות של Service Locator שמקלות על המפתח, ועם הזמן החליטו להחליף את סדר האחריויות: במקום שמחלקה תגדיר למה היא זקוקה ("the artist"), מחלקת ה Service Locator תתפוס פיקוד והיא "תזריק" לצרכן השירות את מה שהוא צריך. מימושים אלו נקראו בהתחלה "Inverse of Control" (בקיצור: IoC), עד שמרטין פאוולר טבע את המונח "Dependency Injection" (בקיצור: DI) אותו אנו מכירים היום.


הנה דוגמה לשימוש ב JSR-330 (הסטנדרט של ג'אווה ל DI):


ה Annotation של Inject@ מסמנת שלבנאי של Michelangelo "יוזרק" המימוש הקונקרטי של המחלקה מסוג ה Painter לה הוא זקוק - מה שמותיר את הקוד של Michelangelo נקי למדי מכל עניין של ניהול הקשרים בין המחלקות.

היכן מוגדר הקשר בין המחלקות? במחלקה אחרת, ייעודית לעניין:


מחלקה זו אינה מוגבלת בתלויות שלה - התפקיד שלה הוא "לשאת בנטל" התלויות של האפליקציה.

הקישור בין Inject@ והפונקציה ()bind נעשית בספריית ה DI בשם Google Guice - ה Reference Implementation הרשמי של JSR-330.

אי אפשר לא להזכיר את Spring - הספרייה שבעצם הפכה את DI לכ"כ נפוץ, והופיעה זמן מה לפני JSR-330 או Guice. ספרינג היא עדיין כנראה ספריית ה DI הנפוצה ביותר בעולם הג'אווה.


ההיסטוריה של ה DI/IoC. מקור


הערה: בעוד המינוח "Dependency Injection" היה ברור יותר מ "IoC" המעורפל-משהו, בד בדד הוא כנראה הותיר גם לאנשים מספיק מרחב לדמיין מהי "הזרקה". הזרקה, למשל, יכולה להיות גם השמה ב Reflection של Private member של מחלקה, לא? למשל כך:



זהו תחביר מקוצר לדוגמה האחרונה. במקום לקבל את הערך כפרמטר לבנאי המחלקה, ושם להציב אותו כ private member - אנו פשוט מציינים את ה Inject@ ישירות על ה private member של המחלקה.

מצד אחד חסכנו שורת קוד משעממת, מצד שני "עקפנו" את ה encapsulation של ג'אווה והתרנו לגשת ל private member של המחלקה.

יש כאלו שיטענו שזו דרך לגיטימית לקצר את הקוד ולכתוב אותו בצורה יותר אלגנטית, אך יש גם חשש מהבעיות שתחביר כזו יכול להציב:
  1. טשטוש הגבולות של מושג ה private בג'אווה.
  2. בעוד בנאי עם הרבה (נאמר 10) פרמטרים יציק לנו, וידרבן אותנו לנהל תלויות בצורה קפדנית / לחלק את המחלקה לכמה מחלקות קטנות יותר ועם התמחות מוגדרת יותר - (מניסיון) לא כ"כ מפריע לנו שיש מחלקה עם 10 פרמטרים עליהם יש Inject@.
  3. שימוש מוגבר ב class members ולא משתנים עם scope מצומצם יותר - כי כ"כ קל להגדיר בדרך זו משתנים.

סה"כ "הזרקה" של משתנים לבנאי המחלקה נחשבת הדרך המועדפת לניהול תלויות.
הנחת היסוד הוא ששימוש ב DI בא להבטיח ניהול תלויות מדויק וקפדני, ולא רק "לקצר את כתיבת הקוד". הדרך הכי קצרה לנהל תלויות היא כנראה שימוש ב new, בכל מקום שרק רוצים.
ולכן, השמה ל private member נראית קצת מוזרה: כמו אכילת קינואה וברוקולי עם צ'יפס מטוגן בצד. מותר, אבל מוזר.



ריבוי צורות (Polymorphism)


אם שמתם לב, הדרך להגדיר קשר בין הממשק למחלקה הקונקרטית (פקודת ()bind) היא על בסיס הטיפוס (class) בג'אווה. בתחילת הדרך נהוג היה דווקא לרשום מחלקה תחת שם (מחרוזת מסוימת) ואז לבקש אותה בחזרה ב Service Lookup / בהזרקה. עם הזמן הגיעו ל 2 תובנות:
  • שם המחלקה הוא כנראה התיאור הברור והצפוי ביותר שלה.
  • השימוש ב class object בשילוב עם Generics מאפשר ליצור ספריית DI / Service Lookup ללא הצורך המרגיז בביצוע downcasting.

מה קורה כאשר רוצים להשתמש בריבוי צורות?


ברור לכם שברגע שהמיפוי הוא לא חד-חד ערכי, ה framework לא יצליח לבצע resolving נכון ב runtime. יש frameworks שמתירים מצבים שהם לא חד-חד ערכיים, בהנחה שהם יצליחו לנחש נכון, ויש כאלו שפשוט זורקים שגיאה בזמן ריצה, למשל: "binding xxx was already configured".

מה עושים? יש לכך כמה פתרונות (ל Spring Framework, למשל, יש כמה דרכים משלה), אך הפתרון המקובל ב JSR-330 הוא להוסיף Qualifiers (מעין תגיות) על ה binding ועל הבקשה (ועל צרכן השירות - בהתאמה), וכך לבצע את ההתאמה: התאמה ע"פ תיאור הכוונה.

רישום של 2 מימושים לממשק Painter, תחת שמות שונים

הנה המימוש המחלקה Michelangelo שמקבלת שני מימושים שונים של Painter:

הדוגמה היא רק בכדי להמחיש את נושא ה DI


DI בהיבט של בדיקות יחידה/אינטגרציה

זווית אישית: כפי שכתבתי בפוסט קודם על בדיקות יחידה, אני מעדיף בבירור את הבניית הקוד כך שלא יידרשו Mock Objects עבור בדיקות יחידה - זו פשוט תקורה מיותרת. השימוש ב Mock Objects כן שימושי בעת ביצוע בדיקות אינטגרציה. יש כל-מיני פרשנויות ל "בדיקות אינטגרציה", אני מתכוון לבדיקות שרצות offline (למשל בתוך ה IDE) אך בודקות את השילוב של כמה מחלקות לידי תסריט גדול יותר.

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

ראינו את גישת ה Qualifiers של DI, שהתאימה לנו עבור מקרים של Polymorphism אך לא תתאים למקרים של בדיקות. צרכן השירות לא יכול לבקש משהו אחד בזמן הרצה רגילה, ומשהו אחר בזמן בדיקות. הוא לא אמור להיות מודע לסוג ההרצה שהוא עובד (אבוי אם כן...).

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



אזור ירוק - הכנסת הסביבה הבדיקה; אזור הכתום - הרצת הבדיקה.
  1. אנו בודקים את מחלקת ה Artist ולכן צריכים מופע שלה. כדי שה DI Framework יוכל לעבוד, אנו צריכים ליצור את Michelangelo בעצמו בעזרת ה Framework.
    יש פה הזרקה ל member. מה שאני לא מרשה לעצמי לעשות בקוד production, אני מרשה לעצמי לעשות בקוד של בדיקות - כדי לכתוב קוד קצר יותר.
  2. אני יוצר mock ל Painter. לא רציתי להתעסק עם ספריית mocking (כמו Mockito או PowerMock) ולכן יצרתי mock כ Anonymous Class.
  3. פונקציה זו היא בעצם inline של הקונפיגורציה. בעוד שבקוד production אנו רוצים להפריד את ה wiring מהקוד, דווקא בבדיקה ה wiring הוא חשוב להבנת הבדיקה, ולכן אני מעדיף שיהיה באותו הקובץ. שוב השתמשתי ב Anonymous Class.
  4. זו הבדיקה עצמה, הפעלה של המתודה ()createArt מול התוצאה הצפויה (הבדיקה עוברת).


סיכום


בפוסט זה סקרנו את נושא ה Dependency Injection ושורשיו, וניסינו להפוך "שימוש אוטומטי" בכלי DI, לשימוש מושכל יותר.

מה השתנה בשנתיים האחרונות שגרם לי לחדש את הפוסט?
אני חושב שבאופן אישי יצא לי להיחשף בעבר ל DI בעיקר ככלי לביצוע בדיקות יחידה - את ניהול התלויות העיקרי עשינו במערכת (הישנה) על גבי JNDI של JEE (סוג של Service Locator). את תמונת עולם זו שיקפתי בפוסט בצורה דיי נחרצת - כפי שהגיב והעיר ויטלי.

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

השמטתי מהפוסט המקורי פסקה שלמה בשם "רגע של ספקנות". אני אזכיר כמה רעיונות מרכזיים בקצרה:
  • אין צורך ב Framework בכדי לבצע DI. בסה"כ, DI הוא רעיון, וה Frameworks השונים רק יוצרים אוטומציה של התהליך.
  • באוטומציה (או Framework) יש יתרון שהוא גם בעיה: היא חוסכת מאיתנו את הצורך בהבנה עמוקה. ללא הבנה עמוקה ניתן לשכוח מדוע אנו עושים מה שאנו עושים (במקרה שלנו - DI) - ולפתע למצוא את עצמנו עושים משהו אחר מהכוונה המקורית.
    אני נוטה בשנים האחרונות להטמיע רעיונות ראשית ללא Framework, בכדי להבין לעומק את הנושא - ורק בסיבוב השני להוסיף את ה Framework שמקל על העבודה. זה כמו ללמוד "חשבון" (מתמטיקה בסיסית) עם מחשבון ביד מהרגע הראשון: האם לא עדיף ללמוד כמה חודשים ללא מחשבון - ורק אז להוסיף אותו?
  • DI הוא לרוב רק חלק קטן במערך המודולריזציה של המערכת. שימוש ב DI לא מבטיח איכות או מודולריות, בפני עצמו. הוא רק כלי (נוסף) להשיג מטרות אלו.
  • אני לא שולל את הרעיון של אי-שימוש ב DI. למשל: להפסיק ליצור interface לכל מחלקה (מלבד מקרים של ריבוי צורות), להסתמך על public vs. private להגדרת הממשק, ולצמצם את השימוש ב DI למינימום האפשרי (למשל: עבור בדיקות אינטגרציה, או ניהול תלויות ברמת השירותים בלבד). בסופו של דבר אנו משלמים לא מעט עבור ההגנה שמספקת גישת ה Program to Interface (בצורה הדקדקנית שלה) וצוותים מסוימים / פרוייקטים מסוימים - יכולים להסתדר מספיק טוב עם פחות הגנות, ועדיף לתת להם להתקדם מהר יותר. 

את דוגמאות הקוד כתבתי ב Guice ולא בספריה הנפוצה יותר Spring Framework בעיקר בגלל ש Guice מצומצם ופשוט יותר. ספרינג מספקת הרבה מאוד יכולות - שיכולות מאוד שימושיות למערכת גדולה ומסובכת, אך לא היו תורמות דבר להבנת הרעיונות הבסיסיים.


שיהיה בהצלחה!




----

[א] עקרון זה מוכר גם כ Separation of Concerns או Segregation of Duties. הכוונה היא שכל יחידת קוד (פונקציה, מחלקה, מודול) יעשו דבר אחד, ודבר אחד בלבד. צד שני של אותו המטבע הוא שכל אחריות תהיה בידיים של יחידת קוד אחת גם כן. כלל זה עוזר ליצור סדר במערכת, ולוודא שמי שכותב את הקוד יראה לנגד עיניו את התמונה המלאה ולא חלקים ממנה (כי כל האחריות נמצאת באותו המקום).


קישורים:

DI קליל ברובי
הפסיכולוגיה של בדיקות תוכנה - מישקו הארווי (היוצר של AngularJS ו jsTestDriver).



8 תגובות:

  1. לא החלטתי עדיין אם אני אוהב את העובדה שבפוסט הכרותי על DI וIOC לא עלה ולו פעם אחת המושג Seperation Of Concerns.

    השבמחק
    תשובות
    1. עברתי על הפוסט וביצעתי שיפורים קלים, הוספתי גם אזכור ל SOC. האם היו לי ייסורי מצפון בעקבות ההערה ?! ;-)

      מחק
  2. פוסט מעניין ומאוד רלוונטי לנו...
    הייתי שמח לראות קצת דוגמאות שמחברות בין DI לבין unit testing

    השבמחק
  3. היי ניסים,

    תודה על ההערה!
    באמת פיספסתי ולקחתי כמובן-מאליו את הדרך בה DI מסייע לבדיקות-יחידה.

    בעיה גדולה בבדיקות יחידה היא התלויות: המצב בו קוד שאנו רוצים לבדוק קורא כמה קריאות למודולים אחרים - שאיננו יכולים לכלול בבדיקה (אם זה בשל פעולות IO / קוד שלא ברשותנו וכו').

    דרך נפוצה (לא) להתמודד עם הבעיה היא לעבור ולעשות בדיקות מערכת / אינטגרציה ולבדוק את כלל המערכת - ולא בדיקה של יחידות סגורות של קוד. כלומר - אלו אינן כבר בדיקות-יחידה.

    דרך נפוצה להתמודד עם הבעיה היא יצירה של Mock Objects, כאלו שאנו כותבים לבד (כי הם יכולים להיות פשוטים) או בעזרת ספריות (כגון EasyMock). יש שמבחינים בין Mock Object, ל Stub, Spy או Dummy Object - אבל זה לא משנה הרבה לעניין זה.

    בסופו של דבר צריך, בזמן בדיקה, "להאכיל" את המחלקה הנבדקת באובייקט המדומה (Mock) במקום באובייקט המקורי. אפשר לעשות את זה ע"י חשיפה של Setters שמחליפים את המצביע לאובייקט - וייקרא רק ע"י הבדיקה, אבל הרבה יותר קל (ואלגנטי) לעשות את זה ע"י DI - להחזיק קונפיגורציה אחת לפרודקשיין וקונפיגורציה אחת לבדיקות.

    תפקידו של ה DI במקרה זה הוא לנהל את כל התלויות שיכולות להכשיל בדיקה, כך שיהיה ניתן להחליף אותן בקלות בתלויות שמאפשרות בדיקה.

    מקווה שההסבר ברור.

    ליאור

    השבמחק
  4. היי ליאור,

    תודה רבה על ההסבר!!

    יצא לי בעברי לכתוב mock objects לבדיקות יחידה והדרך שבה עשינו את זה הייתה באמצעות שימוש ב reflection לצורך 'הזרקת' אובייקט הבדיקה/פרודקשיין. עכשיו אני מבין שזה בדיוק מה שתארת ב בהקשר של DI.

    בכל אופן, עכשיו זה ברור :-)
    נושא מעניין.

    תודה,
    ניסים

    השבמחק
  5. הי ליאור,
    פוסט מאוד מעניין ( כרגיל:-) ) אך אני מוכרח לציין שאני לא מסכים אם כמה מהטיעונים המרכזיים:
    1. לגבי תשתיות Mocking, אני חושב שערבבת פה שני תחומים. DI מאפשר לך להתמודד בקלות עם הזרקה של לוגיקה מסויימת בזמן ריצה, לעומת זאת, תשתיות Mocking למיניהן עוזרות לך לייצר test doubles עם דגש על behavior driven validation. כך שאיני רואה יחס גרירה סיבתי בטענה ש DI יוצר מערכת שהיא יותר Testable ע"פ שימוש ב- Mock objects, שני השיטות פשוט מטפלות בבעיות שונות.
    2. ישנן Mocking frameworks אשר תוקפות גם את עניין ההזרקה, Typemock ב- .Net ו- PowerMock ב- java. הן מאפשרות לך לכתוב קוד אשר לא מתעקם עקב הצורך להפוך אותו ליותר testable. בעוד שזה לא בהכרח דבר טוב בפני עצמו (סך הכל, קוד שהוא testable הוא בדרך כלל גם קוד מעוצב נכון) זה מקל מאוד על כתיבת unit test'ים לקוד legacy. וזה לא דבר של מה בכך, אני מרגיש מאוד בטוח באמרי שמרבית הקוד כיום הוא קוד כזה. אם אנסה להפוך אותו ל- testable, או שאצטרך לעשות שינויים בו עצמו או לעטוף אותו במבנים "נחמדים", מלאכה שלא אני ולא אף מפתח שאני מכיר ששים אליה.
    לא מזמן ביצעתי הטמעה של power mock אצלנו בצוות רק כי הבנתי שהנסיון שלי לכתוב unit test'ים לחתיכת קוד שנכתבה לפני חמש שנים יחתוך בערך אותה כמות זמן מהחיים שלי :-) כמובן שאם כוח גדול באה גם אחריות גדולה.
    3. לגופו של עניין, DI דווקא מאוד עוזר לארכיטקטורות plugin, הרבה פעמים, אפילו אם המנוע העיקרי תומך ברגיסטרציה פרוגרמטית של מודולים, לגופים שכותבים את ה- plugin'ים אין גישה ישירה אליו. (לפעמים גופים אלה נמצאים אף מחוץ לארגון).
    4. דווקא לגבי התמריץ של ניהול תלויות בקוד. Dependency Injection מסייע לך לשמור על עקרון ה- Dependency Inversion, אשר אומר כי high level modules should not depend on low level modules. למשל Business Logic אמור (רצוי) להיות תלוי באבסטרקציה של DAL ולא במימוש קונקרטי. יתרה מזו, DI מאפשר לך ליצור תלויות אשר לא היית יכול ליצור מלכתחילה עקב dependency cycles. נכון שעדיף להמנע מלכתחילה מתלויות כאלה, אבל אחרי שיש לך כמה עשרות packages, זה לא כל כך פשוט וזה יכול לתקוע אותך להרבה זמן.

    לסיכום, איני מתווכח על העובדות אבל לדעתי הקביעה שלך נחרצת מדי. DI מאפשר לך לעשות החלטות ארכיטקטוניות מראש והחלטות פרגמטיות במהלך מחזור החיים של התוכנה, לא רק בבדיקות. כמו כן, חשוב לי לציין כי אין כאן שחור ולבן והפער בינינו כנראה נובע יותר משיקולים אידיאולוגיים (המתוארים במאמר האיקוני של Martin Fowler, Mocks Aren't stubs)


    השבמחק
    תשובות
    1. וטילי,

      תודה רבה על התגובה המעניינת והמנומקת-היטב.
      אני שמח להחשף לנקודת מבט נוספת על העניין.

      המקור לטענה שלי היא:
      1. עבדתי עם DI כמה שנים טובות. כשאני מסתכל אחורה וחושב מתי הוא באמת שינה משהו לטובה - אז זה היה רק בבדיקות יחידה ("לדחוף Mock Objects", בגדול). כל הרעיונות האחרים היו יפים על הלוח - אבל נראה לי שהשיגו בפועל יותר תקורה מתועלת. ת'כלס.
      2. רציתי להגיד משהו - וזה אומר לסגור ולבטל את הספקות האחרים, שתמיד קיימים.

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

      שוב תודה,
      ליאור

      מחק