שלום לכולם,
היום אנחנו ממשיכים במסע שלנו ברחבי ה-design pattern.
והפעם נדבר על template method pattern.
במהלך המסע שלנו, אנחנו עבדנו הרבה על אנקפסולציה.
עטפנו יצירה של אובייקט, קריאה לפונקציות, ממשקים מורכבים, ברווזים, פיצות.
מה עוד אנחנו יכולים לעטוף?
הפעם אנחנו הולכים לעטוף חלקים של אלגוריתם, כך ש-sub classes יוכלו להתחבר מתי שהן רק רוצות
ואפילו נלמד על עקרון חדש, שקיבל השראה מהווליווד!
כל הסידרה הזאת על design pattern מבוססת על הספר הנהדר Head First Design Patterns
אין דבר כזה יותר מדי קפה
מאז שהפכתי להיות הורה, למדתי שאין דבר כזה יותר מדי קפה
הילדים לא נותנים לישון בלילה, וביום צריך להיות ערני כדי לתפוס באגים ולכתוב פיצ'רים
ומה הכי חשוב בקפה?
קפאין כמובן!
אבל ב
חורף לפעמים אני אוהב לשתות גם תה.
ולאחרונה שמתי לב שאנחנו מכינים קפה ותה בצורה מאוד דומה:
הוראות להכנת קפה: 1. הרתיחו מים 2. שימו קפה בתוך כוס 3. מזגו את המים לתוך הכוס 4. הוסיפו סוכר וחלב לפי הצורך הוראות להכנת תה: 1. הרתיחו מים 2. שימו תיון בתוך כוס 3. מזגו את המים לתוך הכוס 4. הוסיפו לימון
נכון שזה נראה מאוד דומה?
אם היינו צריכים לכתוב קוד שייצג את התהליך הזה הוא היה נראה בערך ככה:
במסע שלנו כבר ראינו לא מעט קוד ולא מעט UML,
בואו קחו רגע, ותנסו לצייר UML כך שיטפל בכפילות בקוד שיש לנו פה
.
.
.
.
סיימתם?
אחלה, הנה הפתרון שלי:

הפונקציות boidWater ו-pourWaterInCup משותפות לשתי המחלקות.
ולכן הן עוברות להיות ב-CaffeineBeverage
אבל ה-addBaseIngredients וה-addCondiments משתנות בין שני המשקאות שלנו
ולכן הן יהיו אבסטרקטיות
האם פספסנו משהו?
האם עשינו עבודה טובה עם ה-design שלנו?
פספסנו משהו?
לקפה ותה יש עוד משהו במשותף?
אם נסתכל טוב, נראה שגם קפה וגם תה משתמשים באותו אלגוריתם:
1. להרתיח מים 2. לשים מרכיב בסיסי בתוך הכוס (תיון או קפה) 3. למזוג את המים הרותחים לתוך הכוס 4. להוסיף תוספות למשקה
צעדים 1 ו-3 הם זהים לחלוטין בשני המשקאות, ולכן הם עברו למחלקה האבסטרקטית
אנחנו יכולים להגיד שכל מי שישתמש ב-CaffeineBeverage, ישתמש באבסטרקציה שלהם
אבל צעדים 2 ו-4 הם ממומשים ב-sub classes, ולכן אין להם אבסטרקציה
אבל בפועל, הם עושים את אותה פעולה, עם משתנים שונים
אז אולי יש דרך שנוכל ליצור לזה אבסטרקציה?
ליצור אבסטרקציה עבור preprareRecipe
בואו נסתכל על האבסטרקציה של prepareRecipe עבור כל אחד מהמשקאות שלנו
הבעיה הראשונה שיש לנו היא שקפה משתמש ב-putCoffeeInCup וב-AddSugarAndMilk
ואילו תה משתמש ב-putTeaBagInCup וב-addLemon:
Tea | Coffee |
---|---|
prepareRecipe | prepareRecipe |
boilWater | boilWater |
putTeaBagInCup | putCoffeeInCup |
pourWaterToCup | pourWaterToCup |
addLemon | addSugarAndMilk |
בתכלס, לשים קפה ולשים תה זה לא שונה.
זה אותה פעולה רק עם פרמטר שונה
אז אנחנו ניצור פונקציה חדשה: putBaseIngridientInCup, ואז נוכל להשתמש באותו שם של פונקציה גם לקפה וגם לתה
ובנוסף, נחליף את הצעד האחרון בפונקציה addCondiments
עכשיו הקוד שלנו יראה ככה:
אחרי שעשינו את הצעד הזה, אנחנו יכולים להסתכל על דוגמה מלאה
נתחיל עם ה-CaffeineBeverage:
קודם כל נשים לב שהמחלקה הזאת היא אבסטרקטית, בדיוק כמו ב-design שעשינו
וגם אפשר לרואת את ה-prepareRecipe שכתבנו למעלה
הגדרנו אותה כ-final כי אנחנו לא רוצים שה-sub classes שלנו יוכלו לשנות את האלגוריתם
בנוסף, הפונקציות addBaseIngridientToCup ו-addCondiments הן אבסטרקטיות
על מנת לאפשר לכל sub class לממש את זה בעצמן
הקוד הבא שלנו יהיה תה וקפה
בדיוק כמו ב-design המחלקה Tea יורשת מ-CaffeineBeverage
והיא צריכה לממש את שתי הפונקציות האבסטרקטיות
אותו דבר לגבי קפה.
אז מה עשינו עד עכשיו?
לסיכום ביניים של התהליך שעשינו עד עכשיו בפוסט:
לקחנו שני מתכונים יחסית דומים, אחד להכנת קפה ואחד להכנת תה
הוצאנו מהם את מה שהיה משותף, ועשינו לזה אבסטרקציה
ובעצם הגדרנו מחלקה ששולטת באלגוריתם של הכנת המשקה – CaffeineBeverage
והיא תלויה ב-sub classses שלה על מנת לבצע צעדים ספיציפיים בתוכו
הכירו את טמפלייט – Template Method
בלי לשים לב ממימשנו את Template Method Design Pattern
אם נסתכל על המבנה של CaffeineBeverage :
prepareRecipie היא ה-tempate method שלנו
למה? בגלל
- היא מתודה בסופו של דבר
- היא מגדירה מהו ה-template, מהו האלגוריתם להכנת המשקאות
ב-template יש לנו את הצעדים של האלגוריתם שאנחנו רוצים לעבוד לפיהם
וכל צעד מיוצג על ידי מתודה נוספת.
חלק מהן מנוהלות על ידי המחלקה הראשית, וחלק על ידי sub class
כל מתודה אשר צריכה להיות מנוהלת על ידי ה-sub class מוגדרת כאבסטרקטית
ה-template method מגדיר את הצעדים של האלגוריתם ומאפשר ל-sub classesלספק את המימוש של צעד אחד או יותר
מה template method נותן לנו?
לפני | אחרי |
---|---|
קפה ותה מנהלים את ההצגה | ה-CaffeineBeverage מנהל את ההצגה. יש לו אלגוריתם והוא מגן עליו |
יש לנו קוד משוכפל בין מחלקות | אנחנו מנצלים את המחלקה הראשית על מנת למנוע שכפולי קוד ככל הניתן |
שינויים באלגוריתם דורשים שיני של כל המחלקות הקיימות, כלומר לשינויים רבים | האלגוריתם חי במקום אחד, ושינוי שלו דורש שינוי במקום אחד בלבד |
על מנת להוסיף משקה חדש, דרושה עבודה רבה של שכפול וכתיבת הרבה קוד | ה-template method pattern מספק לנו תשתית כך שמשקאות חדשים יכולים להתחבר אליה, ולכן משקאות חדשים מממשים מעט מתודות |
הידע על האלגוריתם, ואיך מממשים אותו, מפוזר בין הרבה מחלקות | הידע של האלגוריתם מרוכז במקום אחד, ותלוי ב-sub classes לצעדים ספציפיים |
הגדרה רשמית של template method pattern
ראינו את template method בפעולה
עכשיו נגדיר אותו בצורה רשמית
ה-template method design pattern מגדיר את השלד של האלגוריתם במתודה. ומשהה את המימוש של חלק מהצעדים ל-sub classes. Template method נותן ל-subclasses להגדיר מחדש חלק מהצעדים, ללא שינוי במבנה האלגוריתם עצמו
המטרה העיקרית של ה-design pattern הזה היא לספק את האלגוריתם ולהגן עליו
מהו האלגוריתם? ראינו שבסופו של דבר זו רק מתודה
אבל המתודה הזאת מגדירה את הצעדים שהלוגיקה של הקוד שלנו צריכה לעשות

ה-AbstractClass מכילה את ה tempate method.
והגדרה של מתודות אבסטרקטיות, אשר מייצגות צעדים שה-subclasses צריכים להגדיר מחדש.
היא עושה שימוש בצעדים הללו, אבל היא מנותקת מהמימוש עצמו, מה שנותן לה גמישות
שימו לב, שיכולות להיות הרבה concreteClass,
כל אחת מממשת את האלגוריתם בצורה שונה.
איך יראה קוד כזה בצורה גנרית?
בואו נסתכל מקרוב על מימוש של ה-design pattern
נתחיל מה-AbstractCalss
כפי שניתן לראות, יש לנו פה מחלקה אבסטרקטית
כלומר אנחנו רוצים שירשו אותה, ושמי שירש אותה יספק מימוש למתודות האבסטרקטיות שלה
בנוסף אנחנו יכולים לראות את ה-template method שלה.
היא מגדירה את סדר הפעולות של האלגוריתם שלנו
ובדוגמה הזאת נתתי שני צעדים שהם אבסטרקטים,
כלומר שה-subclasses צריכים לתת את המימוש שלהם
ועוד צעד אחד שממומש במחלקה הזאת.
כלומר שהוא משותף לכל מי שמממש את האלגוריתם הזה
template method with hooks
יש לי חידה בשבילכם
תסתכל על הקוד הבא, ותנסו לחשוב מה השינוי שעשיתי
ובשביל מה עשיתי אותו?
שמתם לב?
הוספתי צעד חדש לאלגוריתם שנקרא hook.
והוא צעד שהמחלקה האבסטרקטית מממשת, כלומר כולם משתמשים באותו צעד
אבל רגע, הצעד הזה לא עושה כלום!
מה הקטע?
בשביל מה הוא שם?
צעדים כאלו נועדו לתת ל- subclasses את האפשרות לדרוס את הצעד הזה
אבל הן לא חייבות לעשות את זה
בעצם hook היא מתודה אשר מוגדרת ב abstarct class אבל מקבלת מימוש ריק
זה נותן ל-subclasses את את היכולת לשחק קצת עם האלגוריתם, אם הן רוצות
אלו נקודות באלגוריתם שאנחנו רוצים לתת ל-subclasses שלנו את האפשרות להוסיף התנהגות
אבל זו התנהגות אופציונאלית, לא כל ריצה של האלגוריתם מחייבת שימוש של הצעדים הללו.
בואו נסתכל על דוגמה, מהכיוון של הקפה
זה המחלקה CaffeineBeverage שהשתמשנו בה בתחילת הפוסט
אבל שינינו אותה קצת, הוספנו לה hook
בעצם אנחנו פתחנו את האופציה לכל subclass לדרוס את ההחלטה הדיפולטיבית של להוסיף דברים למשקה
כי מי אמר שכולם רוצים?
המימוש הדיפולטיבי שלנו הוא להוסיף
אבל אם אני רוצה, אני יכול לבטל את האופציה.
הנה למשל, המחלקה CoffeeWithHook אשר דורסת את ההחלטה הדיפולטיבית:
כאן אנחנו דורסים את ההחלטה הדיפולטיבית להוסיף דברים לקפה
פה אנחנו מבקשים מהמשתמש שלנו להחליט אם הוא רוצה.
שאלות טובות
אנחנו נשתמש במתודות אבסטרקטיות עבוד צעדים שה-subclasses חייבות לממש,
ונשתמש ב-hook כאשר זה נתון לבחירה.
יש מעט שימושים ל-hooks.
כמו שכבר כתבתי, זה מאפשר ל-subclass לשנות צעד אופציונאלי באלגוריתם
או לדלג עליו לגמרי.
אופציה נוספת היא לאפשר ל-subclass להגיב לצעד באלגוריתם שעומד לקרות או כבר קרה.
לדוגמה:
אם באלגוריתם שלנו לפעמים מסדרים מחדש רשימה,
אז hook יכול להיות נקודה בה subclass מגיב להאם שינו את הסדר
זוהי נקודה מאוד חשובה.
כן, כדאי לעשות את זה
אבל לפעמים אין לנו הרבה ברירות, כי הצעדים שלנו הם לא גנריים.
יש לנו פה tradeoff, ככל שהאלגוריתם שלנו יותר גנרי, ככה הוא יותר גמיש.
עקרון הוליווד – The Hollywood Principle
אמרתי בהתחלה שה-template method קיבל השראה מהוליווד
אבל לא סיפרתי לכם עד עכשיו מהי
ובכן הכירו את עקרון הוליווד:
אל תתקשר אלינו, אנחנו נתקשר אליך
קל לזכור נכון? אבל מה הקשר ל-template?
העקרון נותן לנו דרך להילחם בריקבון של תלויות – dependecy rot.
מה זה ריקבון של תלויות?
ריקבון של תלויות קורה כאשר יש לנו high level component אשר תלויה ב-low level component אשר תלויה ב high level component אשר תלויה ב side way componentn אשר תלויה……
הבנתם את הרעיון.
כאשר הריקבון מתקבע בקוד, זה עבודה לא פשוטה להוציא אותו משם.
לא פשוטה בכלל
עם עקרון הוליווד, אנחנו מאפשרים ל-low level component לחבר את עצמן למערכת (to hook )
אבל ה-high level component מחליטה מתי ואיך צריכים אותן.
במילים אחרות, ה-high level component אומרת ל – low level component:
אל תקראי לי, אני אקרא לך
אותו דבר קורה גם ב-template method pattern
ה-template method מאפשרת לכל ה -subclasses להתחבר אליה
אבל היא מחליטה מתי היא קוראת להן, ואיך
סיכום
בפוסט הזה הכרנו גם את template method pattern
וגם את עקרון הוליווד
ה-teplate method מגדיר את צעדי האלגוריתם, ומאפשר ל-subclasses לממש חלק מהצעדים
הוא יכול להגדיר צעדים שמשותפים לכולם, ובכך למנוע שכפול של קוד
הדרך שבה הוא דורש מימוש אצל ה subclasses הוא בשימוש עם מתודות אבסטרקטיות
ראינו גם שיש דרך לאפשר צעדים אופציונליים,
על ידי שימוש ב-hooks.
ומה הקשר שלהם להוליווד
אם הגעתם עד לפה, אשמח שתעזרו להרחיב את התפוצה של הבלוג,
על ידי שיתוף של הפוסט
דוגמאות מלאות של הקוד אפשר למצוא כאן
וכמו תמיד, אשמח לקבל כל שאלה, הערה או הארה שיש לכם