The Dependency Inversion Principle

שלום לכולם,
בפעם הקודמת למדנו ביחד על factory design pattern.
לפני שתמשיכי לקרוא, אני ממליץ בחום לקרוא את הפסוט הקודם על facotry.
הרבה מדוגמאות הקוד שיופיעו בפוסט הנוכחי מבוססות על הפוסט הקודם


גם היום אנחנו נמשיך את המסע שלנו ברחבי ה-design pattern.
בפוסט הזה אנחנו נלמד על עקרון עיצובי חדש: The Dependency Inversion principle.

כמו כל הסדרה על design patterns גם הפוסט הזה מבוסס על הספר Head First Design Patterns

בואו נסתכל על תלויות של אובייקטים

כאשר אנחנו מאתחלים אובייקט, אנחנו יוצרים תלות עם ה-concrete class שלו.
את זה כבר ראינו בפוסט הקודם על factory
אבל לטובת אלו שדילגו, הנה קוד מאוד מאוד תלוי:

אנחנו כמובן נתעלם מזה שהתעלמנו בערך מכל עקרון של OO
בכמה concrete class של Pizza המחלקה למעלה תלויה בהן?
ואם היינו מוסיפים עוד סגנון? למשל סגנון קליפורניה, מה תהיה התשובה אז?

קחו רגע לחשוב לפני שאתם ממשיכים
.
.
.
.

יש לכם תשובה? כמה?
בגרסה הנוכחית ה-class הזה תלויה ב-8 מחלקות שונות
ואם היינו מוסיפים את קליפורניה, היינו תלויים ב12.

אם נצייר דיאגרמה תלויות (כלומר חץ עובר מהתלוי אל מי שהוא תלוי בו)
זה יראה ככה

גרף תלויות בין PizzaStore לבין סוגי פיצה
גרף תלויות בין PizzaStore לבין סוגי פיצה

הגרסה למעלה של PizzaStore תלויה לחלוטין בכל סוגי הפיצות כיוון שהיא יוצרת אותן ישירות
אם האימפלמנטציה של המחלקות הללו תשתנה, אז יש סיכוי שאנחנו נצטרך לשנות את PizzaStore
בגלל שכל שינוי של Pizza משפיע על PizzaStore, אנחנו יכולים להגיד ש-PizzaStore תלויה בבכל הפיצות

בואו נהפוך הכל

אני מניח שזה די ברור שאנחנו לא רוצים שהקוד שלנו יראה ככה
ויש לנו אפשרות לשנות את זה.
על ידי שימוש ב design principle חדש, שנקרא Dependency Inversion Principle.

הקוד צריך להיות תלוי באבסטקרציות.
הקוד לא צריך להיות תלוי ב concrete classes


במבט ראשוני העקרון הזה נראה מאוד דומה לעקרון code to an interface
הוא באמת דומה.
אבל העקרון של dependency inversion הוא הרבה יותר חזק.
הוא דורש שה-high level component לא יהיו תלויות ב-low level components שלנו
שתי הרמות הללו צריכות להיות תלויות באבסטרקציה


High Level Component זו מחלקה אשר ההתנהגות שלנו מוגדרת לפי low level component
למשל בקוד שלנו, PizzaStore נחשב כ high level component כיוון שההתנהגות שלה (לקראת הסוף),
מבוססת על low level component שהן הפיצות שלנו

אוקי, אבל מה זה אומר?
אם נסתכל על הדיאגרמת תלויות שראינו מקודם, ה-PizzaStore תלויה ב-concrete classes
בעקרון אומר לנו שאנחנו לכתוב קוד, כך ש-PizzaStore תהיה תלוי באבסטרקציה.
וגם כל הפיצות שלנו צריכות להיות תלויות באבסטקרציה

איך אפשר לעשות את זה?

הבעיה העיקרית של PizzaStore היא שהיא תלויה בכל אחת מהמופעים של Pizza, כיוון שהיא מייצרת אותם בעצמה.
איך אנחנו יכולים להוציא את היצירה של ה-concrete classes מתוך PizzaStore?
כמו שהסברתי בפוסט הקודם (קראתם? אם לא רוצו לקרוא ואז תחזרו לפה), אנחנו יכולים להשתמש ב factory כדי להשיג את זה

אז אחרי שאנחנו ממשים את ה-factory שלנו, הגרף נראה ככה

גרף תלויות אחרי שיש לנו factory

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

אבל רגע, דיברנו על תיקון של התלות (dependency), אבל איפה החלק של ההיפוך (inversion)?
ההיפוך נמצא בשם העקרון, כי הוא גורם לנו לחשוב בצורה הפוכה ממה שאנחנו חושבים בדרך כלל ב-OO.
אם נסתכל על הדיאגרמה שלנו, נוכל לראות שיש לנו low-level component שתלויה ב-high level abstraction?
ובנוסף גם ה-high level component שלנו תלוי באותה אבסטרקציה.
בגרף הראשון, היה לנו top down. כלומר כל מה שבחלק העליון היה תלוי בחלק התחתון
אבל עכשיו, יש לנו תלות בשני הכיוונים

כדי להבין את זה טוב יותר בואו נסתכל על התהליך הנפוץ כאשר אנחנו מעצבים תוכנה
להזכירכם, אנחנו ממדלים פיצריה
אז קודם כל אנחנו חשבנו על החנות עצמה, וחשבנו מה היא תצרטך לעשות
אמרנו שהיא תצטרך להכין סוגים שונים של פיצות, אבל יהיו תהליכים זהים
זוהי גישה של top down, והיא זו שהובילה אותנו בהתחלה לבעיה שה-PizzaStore הכירה את כל ה-concrete classes.

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

כמה כללי אצבע

הנה כמה כללי אצבע שיעזרו לכם לעקוב אחרי dependency inversion principle:

  • אף משתנה לא מחזיק רפרנס ל-concrete class (למשל אם משתמשים בהשמה עם new ביחד)
  • אף מחלקה לא יורשת מ-concrete class: אם אתם יורשים מ-concrete class, אתם יוצרים תלות באותה מחלקה. יורשים אך ורק מאבסטרקציות
  • אף פונקציה לא דורסת מימוש של פונקציה מהמחלקת אב שלה: אם דרסתם את המימוש, אז המחלקת אב שלכם היא לא ממש אבסטרקציה


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

סיכום

הפעם זהו פוסט יחסית קצר, שדיבר על dependency inversion principle
הסברתי מדוע יצירה של מחלקות יוצרת תלות
וגם למדנו ביחד איך חשיבה של bottom up עוזרת לנו ליצור את האבסטרקציות שאנחנו צריכים
ולמדנו כמה כללי אצבע שיעזרו לנו לעמוד בעקרון הזה

כמו תמיד, אשמח לקבל כל שאלה, הערה או הארה שיש לכם

1 מחשבה על “The Dependency Inversion Principle”

השאר תגובה

Scroll to Top