תבנית המצב
חלק מהיחידה תכנות מונחה עצמים במסלול ה-GO של Coddy. שיעור 95 מתוך 107.
תבנית State מאפשרת לאובייקט לשנות את התנהגותו כאשר מצבו הפנימי משתנה, כך שנראה כאילו האובייקט שינה את המחלקה שלו. בעוד שתבנית Template Method שולטת בשלבי האלגוריתם, תבנית State עוטפת התנהגות ספציפית למצב באובייקטים נפרדים ומאצילה את הפעולות למצב הנוכחי.
ב-Go, נגדיר ממשק של מצב וניצור מצבים קונקרטיים שמממשים התנהגויות שונות:
type State interface {
Handle(d *Document) string
}
type Document struct {
state State
}
func (d *Document) SetState(s State) {
d.state = s
}
func (d *Document) Publish() string {
return d.state.Handle(d)
}כל מצב קובע מה קורה ומהו המצב הבא:
type DraftState struct{}
func (s DraftState) Handle(d *Document) string {
d.SetState(ModerationState{})
return "Draft submitted for moderation"
}
type ModerationState struct{}
func (s ModerationState) Handle(d *Document) string {
d.SetState(PublishedState{})
return "Moderation approved, now published"
}
type PublishedState struct{}
func (s PublishedState) Handle(d *Document) string {
return "Already published"
}אותה קריאה למתודה מניבה תוצאות שונות בהתאם למצב הנוכחי:
doc := &Document{state: DraftState{}}
fmt.Println(doc.Publish()) // הטיוטה הוגשה לבדיקה
fmt.Println(doc.Publish()) // הבדיקה אושרה, כעת המסמך מפורסם
fmt.Println(doc.Publish()) // כבר פורסםתבנית State אידיאלית לאובייקטים עם מצבי פעולה מובחנים, כגון תהליכי עבודה של עיבוד הזמנות, רכיבי ממשק משתמש או מטפלי חיבורים, שבהם ההתנהגות תלויה לחלוטין במצב הנוכחי.
אתגר
קלבואו נבנה מערכת תמיכה בכרטיסים באמצעות תבנית State! תיצרו כרטיס תמיכה שיעבור בין שלבים שונים: פתיחה, טיפול ולבסוף פתרון. בכל מצב ייקבעו הפעולות האפשריות ומה יקרה בהמשך.
תארגנו את הקוד בשלושה קבצים:
state.go: הגדירו ממשק מצב ואת המצבים הקונקרטיים שמייצגים כל שלב במחזור החיים של כרטיס.צרו ממשק
TicketStateעם מתודהHandle(t *Ticket) stringשמעבדת את הכרטיס ועשויה להעביר אותו למצב הבא.ממשו שלושה מצבים:
OpenState: בעת הטיפול, מעביר את הכרטיס ל-InProgressStateומחזירTicket opened, assigning to support teamInProgressState: בעת הטיפול, מעביר את הכרטיס ל-ResolvedStateומחזירWorking on ticket, issue resolvedResolvedState: בעת הטיפול, נשאר באותו מצב ומחזירTicket already resolved
ticket.go: צרו מבנה כרטיס שמכיל את המצב הנוכחי ומאציל לו את ההתנהגות.בנו מבנה
Ticketעם שדהID(מחרוזת) ושדהstate(TicketState). הוסיפו את המתודות האלה:SetState(s TicketState)כדי לשנות את המצב הנוכחי של הכרטיסProcess() stringשמאצילה את הפעולה למתודת Handle של המצב הנוכחי
צרו בנאי
NewTicket(id string) *Ticketשמחזיר כרטיס שמתחיל במצבOpenState.main.go: הדגימו כיצד אותה פעולה מניבה תוצאות שונות בהתאם למצב הנוכחי של הכרטיס.קראו מזהה כרטיס ואת מספר הפעמים לעבד את הכרטיס. צרו כרטיס חדש עם המזהה הזה, ואז קראו ל-
Process()את מספר הפעמים שצוין, והדפיסו כל תוצאה בשורה נפרדת.
הקלטים הבאים יסופקו:
- שורה 1: מזהה הכרטיס
- שורה 2: מספר הפעמים לעבד את הכרטיס
לדוגמה, עבור:
TKT-001
3הפלט שלכם צריך להיות:
Ticket opened, assigning to support team
Working on ticket, issue resolved
Ticket already resolvedועבור:
TKT-500
5הפלט שלכם צריך להיות:
Ticket opened, assigning to support team
Working on ticket, issue resolved
Ticket already resolved
Ticket already resolved
Ticket already resolvedועבור:
ISSUE-42
1הפלט שלכם צריך להיות:
Ticket opened, assigning to support teamשימו לב שקריאה ל-Process() באותו כרטיס מניבה בכל פעם תוצאות שונות: התנהגות הכרטיס משתנה כשהוא עובר בין מצבים. לאחר שהכרטיס נפתר, הוא נשאר במצב פתור, בלי קשר למספר הפעמים שתעבדו אותו. נראה שאובייקט הכרטיס משנה את התנהגותו, אבל למעשה הוא מאציל אותה לאובייקטים שונים של מצב!
נסו בעצמכם
package main
import (
"fmt"
)
func main() {
// קרא את הקלט
var ticketID string
var numProcesses int
fmt.Scanln(&ticketID)
fmt.Scanln(&numProcesses)
// TODO: צור כרטיס חדש עם ה-ID הנתון
// TODO: עבד את הכרטיס מספר הפעמים שצוין
// והדפס כל תוצאה בשורה נפרדת
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות OOP ב-Go
קבצים חיצונייםסביבת העבודה והמודולים של Goחבילות וייבואשמות מיוצאים לעומת שמות לא מיוצאיםמבוא ל-OOP ב-GoStructs כמחלקותהגדרת מתודות עבור Structsמקבלי Pointer לעומת Valueאתחול Structפונקציות Constructorחזרה – מחשבון פשוט4ממשקים
מבוא לממשקיםמימוש משתמעממשק כחוזהממשק ריק (any)אימות טיפוסמתג טיפוסיםהרכבת ממשקיםממשקי Stringer ו-Errorסיכום – מחשבון צורות7כימוס
שדות מיוצאים לעומת שדות לא מיוצאיםכימוס ברמת החבילהמתודות Getter ו-Setterהסתרת מידע ב-Goסיכום – רשומות תלמידים10Generics (Go 1.18 ומעלה)
מבוא ל־Genericsפרמטרים של טיפוסיםאילוצי טיפוסיםמבנים גנרייםפתרון עוקף למתודות גנריותסיכום – אוסף גנרי2העמקה בסוגים וב-Structs
סוגים בסיסיים ומורכביםהגדרות של סוגים מותאמים אישיתתגיות StructStructs אנונימייםStructs מקונניםערכי אפס וברירות מחדלסיכום – ספר אנשי קשר5קומפוזיציה במקום ירושה
למה ב-Go אין ירושהיסודות הטמעת Structקידום מתודותהטמעת כמה Structsהטמעה לעומת אגרגציההסתרת מתודות מוטמעותסיכום – היררכיית עובדים8טיפול בשגיאות ו-OOP
ממשק errorסוגי שגיאות מותאמים אישיתעטיפת שגיאות (fmt.Errorf)שגיאות Sentinelerrors.Is() ו-errors.As()Panic, Defer ו-Recoverסיכום – מנתח קבצים11ספרייה סטנדרטית ותכנות מונחה עצמים
io.Reader ו-io.Writersort.Interfaceהממשק fmt.Stringerencoding/json עם מבניםהממשק http.Handlerסיכום – מודלים של REST API14תבניות עיצוב חלק 2
תבנית הפקודהתבנית המתאםתבנית העיצובתבנית שיטת התבניתתבנית המצבתבנית המורכבMiddleware בתור Decorator3מצביעים וזיכרון
יסודות המצביעים ב-Goמצביעים למבניםהעברה לפי ערך לעומת לפי הפניההפונקציה new()איסוף אשפה ב-Goסיכום - בונה רשימה מקושרת6פולימורפיזם ב-Go
פולימורפיזם באמצעות ממשקיםטיפוס ברווז ב-Goכללי התאמה לממשקאוספים פולימורפייםהזרקת תלויותסיכום - מעבד תשלומים9מקביליות ו-OOP
יסודות GoroutinesChannels ותקשורתChannel עם חוצץ לעומת ללא חוצץפקודת Selectsync.Mutex ו-sync.RWMutexsync.WaitGroupתכנון Struct בטוח לשימוש במקבילסיכום - מאגר עובדים12מושגים מתקדמים בתכנות מונחה עצמים
דפוס האפשרויות הפונקציונליותדפוס Builder ב-Goשרשור מתודותכינויי טיפוסים לעומת הגדרותיסודות Reflectionסקירה כללית של יצירת קודתרגלו בעצמכם: קומפיילר Go אונליין