השבוע אנחנו נמשיך במסענו ברחבי ה-design patterns
והפעם אנחנו נדבר על State Design Pattern.
כמו כל הסדרה הזאת, גם הפוסט הזה מובסס על הספר הנהדר Head First Design Patterns
זו עובדה ידועה, שה –Strategy וה-State patterns הם תאומים שהופרדו בלידתם.
בזמן שאנחנו משתמשים ב-Strategy על מנת לעזור לאובייקטים לנהל את ההתנהגות שלהם בצורה יותר גנרית
הסטייט נועד לשנות את ההתהגות שלהם על ידי שינוי המצב הפנימי שלהם.
למרות השוני בתוצאה, העיצוב של שניהם מאוד דומה.
איך זה יכול להיות?
ממכונת מסטיק אל הקוד
מכירים את המכונות מסטיק הישנות שהיינו משחקים איתם?
היינו שמים מטבע והיה יוצא מסטיק, או כדור קופצני או כל דבר אחר בעצם.
ובכן מסתבר שהיום הם עבור להיות מכונות הייטקיות.
היצרנים גילו שאם הם ישימו מעבד בתוך המכונות הללו, הם יהיו מסוגלים להגדיל את המכירות שלהם.
ככה נראה התרשים של המערכת:
מהתרשים הזה אפשר לראות שאנחנו באמת מדברים פה על מצבים של המערכת
שכל פעולה מעבירה אותנו ממצב אחד למצב אחר.
כל עיגול בתרשים הזה הוא מצב של המערכת.
למשל העיגול "אין מטבע" הוא המצב ההתחלתי של המערכת שלנו
המכונה מלאה, ומחכה שמישהו ישים מטבע על מנת שיהיה אפשר לסובב את העיגול מתכת כדי להוציא מסטיק.
והחץ שמעביר אותנו מהעיגול "אין מטבע" לעיגול "יש מטבע" הוא נקרא המרה ( או transition), כלומר תזוזה ממצב אחד למצב אחר
מתרשים לקוד
אז יש לנו את התרשים הנחמד הזה, אבל איך אנחנו בונים את התבנית של הקוד שלנו?
ובכן השלב הראשון הוא לאסוף את כל המצבים שלנו.
במקרה שלנו יש 4:
- אין מטבע
- יש מטבע
- ביצוע של מכירה
- נגמרו המסטיקים
אחרי זה יש ליצור משתנה אשר יחזיק את המצב הנוכחי של המערכת
ובנוסף ליצור ערכים מוגדרים עבור כל המצבים.
אפשר לעשות את זה בכמה דרכים בג'אווה: למשל static final int או Enum
רביעיית המשתנים בהתחלה אלו הקבועים שלנו, אשר מייצגים את המצבים במערכת
ואילו המשתנה state הוא זה שיחזיק את המצב הנוכחי.
השלב הבא שלנו הוא לאסוף את כל הפעולות שיש לנו בתרשים.
במקרה הזה יש לנו 4
- להכניס מטבע
- להוציא את המטבע
- להוציא מסטיק
- לסובב את המטבע
עבור כל פעולה כזאת, אנחנו ניצור מתודה.
למשל נסתכל על הפעולה "להכניס מטבע":
את הקוד המלא של העיצוב הזה אתם יכולים לראות כאן
והנה חוזר השינוי
ידעתם שזה מגיע. תמיד הוא מגיע.
הבקשה לשינוי.
הפעם מבקשים מאיתנו להוסיף את האופציה הבאה:
אחד מכל 10 זוכה במסטיק חינם!!
התרשים החדש שלנו יראה ככה
במצב הנוכחי של הקוד, אנחנו נצטרך להוסיף מצב חדש, שאנחנו נקרא לו WINNER
ואז אנחנו נצטרך לגעת בכל פונקציה קיימת על מנת לבדוק האם המעבר למצב החדש הוא תקין או לא.
אז מה אנחנו יכולים להגיד על המערכת הנוכחית שלנו?
- הקוד שלנו לא עומד בעיקרון ה Open-Close.
- לא הכמסנו שום דבר שמשתנה במערכת שלנו
- זה לא בדיוק Object Oriented
- שינויים נוספים יגררו שינויים נרחבים בקוד הקיים – עם סיכוי להכנסת באגים חדשים בקוד שעובד
העיצוב החדש
המטרה של העיצוב החדש הוא לעמוד בכללים שדיברנו עליהם עד עכשיו.
אנחנו נרצה להכמיס את המצבים – הם אלו שמשתנים לנו.
ולכן אלו הפעולות הכלליות שנרצה לעשות:
- קודם כל, נרצה להגדיר interface עבור ה-state אשר יגדיר מתודה עבור כל פעולה במערכת שלנו.
- אחרי זה, אנחנו ניצור מחלקה ממשית עבור כל מצב במערכת שלנו. המחלקות הללו יהיו אחראיות על ההתנהגות של המערכת בכל מצב.
- לבסוף, אנחנו נזרוק את כל הקוד if else שיש לנו, ונעבור לעבוד עם המחלקות.
הנה התרשים UML שלנו:
ככה למשל תראה המחלקה NoQuarterState
כפי שניתן לראות, זה הרבה יותר קריא בצורה הזאת.
השליטה בקוד היא קלה יותר.
נשים לב שבבנאי אנחנו מקבלים רפרנס למכונה
וכאשר אנחנו מבצעים פעולה אשר אמורה להעביר למצב אחר, אנחנו מעדכנים את המצב.
עכשיו בואו נסתכל שוב על המכונת מסטיקים
אפשר לראות שאת ה-static final int המרנו במחלקות החדשות שיצרנו.
יש לנו משתנה count על מנת לספור כמה מסטיקים יש במכונה, והוא מאותחל בבנאי
אם יש לנו מסטיק אחד או יותר אנחנו נעבור למצב של אין מטבע
אבל אם נשארנו בלי מסטיקים כבר בהתחלה, אנחנו נעבור למצב שנגמרו המסטיקים.
במצב הנוכחי הפעולות שלנו הן הרבה יותר קטנות וקלות לקריאה.
הכירות את State Design Pattern
אז בלי לשים לב מימשנו את ה-state design pattern.
הנה ההגדרה הרשמית שלו:
ה-state design pattern מאפשר לאובייקט לשנות את ההתנהגות שלו כאשר המצב הפנימי שלו משתנה. זה יראה כאילו האובייקט שינה את המחלקה שלו
החלק הראשון הוא מאוד הגיוני, ממה שראינו עד עכשיו.
כיוון שהתבנית הזאת מכמיסה את ה-state למחלקות נפרדות, ומאפשרת לאובייקט להגיד מה המצב הנוכחי שלו.
מכונת המסטיקים משנה את ההתנהגות שלה, בהתאם למצב בו היא נמצאת.
ובכל רגע נתון אנחנו יכולים לדעת מה המצב שלו.
אבל מה לגבי החלק השני?
מה המשמעות של "שינה את המחלקה שלו"?
אם מתסכלים על זה מהצד של הקליינט, האובייקט שאצלנו ביד משנה את ההתנהגות שלו לגמרי
זה כאילו אתחלנו את האובייקט מחדש, ממחלקה אחרת.
עכשיו בואו נסתכל על התרשים UML של הסטייט:
ה-Context זו המחלקה אשר יכול להיות לה כמה מצבים שונים.
בדוגמה שלנו זוהי המכונת מסטיקים.
ה-State הוא ממשק אשר מגדיר התנהגות עבור כל המצבים האפשריים.
כאשר ה Context קורא ל-request, מאחורי הקלעים אנחנו קוראים ל – state.handle()
זוכרים שאמרתי בהתחלה שה-state וה-strategy הם תאומים?
ובכן, אם תזכרו בתרשים UML של strategy תראו שמדובר באותו תרשים בדיוק.
אבל יש הבדל בין שני התרשימים הללו.
ב-state יש לנו קבוצה של התנהגויות, אשר מוכמסת באובייקטים מסוג state.
בכל זמן נתון הקונטקסט משתמש באחד מהמצבים הללו.
עם הזמן המצב האקטיבי משתנה, ובעקבותיו גם ההתנהגות של הקונטקסט.
עם strategy, הקליינט מגידר מהי ההתנהוגת הרצויה.
זה נכון שיש לנו גמישות להחליף את ההתנהגות בזמן ריצה, אבל לדעתי יש פה הבדל בין זה לבין שינוי מצב אקטיבי של המערכת.
באופן כללי, אתם יכולים לחשוב על strategy כעל חלופה גמישה לירושה.
אם אתם משתמשים בירושה כדי להגדיר התנהגות של מחלקה מסוימת
אז אתם נתקעים עם ההתנהגות הזאת גם אם אתם רוצים לשנות אותה.
עם strategy, אתם יכולים לשנות את ההתנהגות על ידי חיבור עם מחלקה אחרת.
ואתם יכולים לחשוב על state כעל תחלופה לקוד מרובה תנאים בקונטקסט שלכם.
בכך שאנחנו מכמיסים את ההתנהגות בתוך מחלקות, אנחנו פשוט יכולים לתת לאובייקט ה-state בקונטקסט מסוים להחליט מה דרך הפעולה המועדפת.
סיכום
הפעם למדנו על ה-state design pattern.
ראינו איך התבנית הזאת מאפשרת לנו התנהגויות שונות, בהתאם למצב נתון.
בניגוד למכונה פרוצדורלית, אנחנו ממדלים את המצב כמחלקה מלאה
הקונטקסט מקבל את ההתהנגות שלו, על ידי חלוקת אחריות למחלקות state שיש לו
על ידי הכמסה של כל מצב למחלקה, אנחנו מצמצמים את השינויים שיהיו בקוד בעתיד.
בנוסף ראינו את הדימיון הגדול בין strategy ובין state, וגם למדנו שstrategy נועד לטפל בירושה
ואילו state נועד לטפל בקוד עם הרבה תנאים בתוכו.
אם הגעתם עד לכאן אשמח לשיתוף של הפוסט ברשתות, על מנת לעזור לבלוג לצמוח
וכמו תמיד, אשמח לקבל כל שאלה, הערה או הארה שיש לכם