תבנית הפקודה
חלק מהיחידה תכנות מונחה עצמים במסלול ה-GO של Coddy. שיעור 91 מתוך 107.
תבנית הפקודה עוטפת בקשה כאובייקט, ומאפשרת לך להגדיר פעולות באמצעות פרמטרים, להכניס אותן לתור או לתמוך בפונקציונליות של ביטול. בעוד שתבנית Strategy עוטפת אלגוריתמים, תבנית Command עוטפת פעולות שלמות יחד עם הפרמטרים שלהן.
ב-Go, אנחנו מגדירים ממשק Command עם מתודה Execute, ואז יוצרים פקודות קונקרטיות שמכילות את כל המידע הדרוש לביצוע פעולה:
type Command interface {
Execute() string
}
type Light struct {
IsOn bool
}
type TurnOnCommand struct {
light *Light
}
func (c *TurnOnCommand) Execute() string {
c.light.IsOn = true
return "Light turned on"
}
type TurnOffCommand struct {
light *Light
}
func (c *TurnOffCommand) Execute() string {
c.light.IsOn = false
return "Light turned off"
}מפעיל מאחסן פקודות ומבצע אותן בלי לדעת מה הן עושות:
type RemoteControl struct {
command Command
}
func (r *RemoteControl) SetCommand(c Command) {
r.command = c
}
func (r *RemoteControl) PressButton() string {
return r.command.Execute()
}היוזם מנותק לחלוטין מהמקבל (האור):
light := &Light{}
remote := &RemoteControl{}
remote.SetCommand(&TurnOnCommand{light: light})
fmt.Println(remote.PressButton()) // האור נדלק
remote.SetCommand(&TurnOffCommand{light: light})
fmt.Println(remote.PressButton()) // האור כבהתבנית Command אידיאלית למימוש מערכות ביטול/ביצוע מחדש, תורי משימות או הקלטת פקודות מאקרו, כשצריך לאחסן, לדחות או להפעיל מחדש פעולות.
אתגר
קלבואו נבנה עורך טקסט עם יכולת ביטול באמצעות תבנית Command! תיצרו פקודות שעוטפות פעולות טקסט, ומאפשרות לעורך לבצע פעולות ולבטל אותן: זהו מקרה שימוש קלאסי שמדגים מדוע פקודות הן כלי רב־עוצמה כל כך.
תארגנו את הקוד בשלושה קבצים:
command.go: הגדירו את ממשק הפקודה ואת הפקודות הקונקרטיות לעריכת טקסט.צרו ממשק
Commandעם שתי מתודות:Execute() stringלביצוע הפעולה ו-Undo() stringלביטולה.ממשו שני סוגי פקודות שפועלים על
*TextEditor:InsertCommand: מחזיקה מצביע לעורך ואת הטקסט להוספה. Execute מוסיפה את הטקסט לתוכן העורך ומחזירהInserted: [text]. Undo מסירה את הטקסט הזה מהסוף ומחזירהUndone insert: [text]DeleteCommand: מחזיקה מצביע לעורך ומספר התווים להסרה מהסוף. Execute מסירה את התווים האלה (ושומרת אותם לצורך ביטול) ומחזירהDeleted: [removed text]. Undo משחזרת אותם ומחזירהUndone delete: [restored text]
editor.go: צרו את עורך הטקסט שלכם (המקבל) ואת המפעיל שמנהל את היסטוריית הפקודות.בנו מבנה
TextEditorעם שדהContentמסוג מחרוזת ועם מתודות ל-Append(text string)ול-DeleteLast(count int) string(מחזירה את הטקסט שנמחק).בנו מבנה
EditorInvokerשמחזיק פרוסה של הפקודות שבוצעו, כהיסטוריה. הוסיפו את המתודות הבאות:ExecuteCommand(cmd Command) string: מבצעת את הפקודה, מוסיפה אותה להיסטוריה ומחזירה את התוצאהUndoLast() string: מסירה את הפקודה האחרונה מההיסטוריה, קוראת למתודת Undo שלה ומחזירה את התוצאה. אם ההיסטוריה ריקה, החזירוNothing to undo
main.go: הדגימו את מערכת הפקודות שלכם באמצעות סדרת פעולות.קראו את מספר הפעולות. עבור כל פעולה, קראו את סוג הפעולה (
insert,deleteאוundo). עבור insert, קראו גם את הטקסט להוספה. עבור delete, קראו את מספר התווים. בצעו כל פעולה דרך המפעיל שלכם והדפיסו את התוצאה. לאחר כל הפעולות, הדפיסו את התוכן הסופי של העורך בפורמטFinal: [content].
הקלטים הבאים יינתנו:
- שורה 1: מספר הפעולות
- עבור כל פעולה: סוג הפעולה, ולאחר מכן נתונים נוספים אם נדרשים (טקסט עבור insert, מספר עבור delete)
לדוגמה, עבור הקלט:
5
insert
Hello
insert
World
delete
3
undo
undoהפלט שלכם צריך להיות:
Inserted: Hello
Inserted: World
Deleted: rld
Undone delete: rld
Undone insert: World
Final: Helloועבור הקלט:
4
insert
Go
insert
Lang
undo
insert
!הפלט שלכם צריך להיות:
Inserted: Go
Inserted: Lang
Undone insert: Lang
Inserted: !
Final: Go!ועבור הקלט:
2
undo
insert
Testהפלט שלכם צריך להיות:
Nothing to undo
Inserted: Test
Final: Testשימו לב שכל פקודה עוטפת את כל הדרוש לביצוע הפעולה שלה וגם לביטולה. המפעיל לא יודע מה הפקודות עושות. הוא רק מבצע אותן ומנהל היסטוריה כדי לאפשר ביטול!
נסו בעצמכם
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// קרא את מספר הפעולות
line, _ := reader.ReadString('\n')
numOps, _ := strconv.Atoi(strings.TrimSpace(line))
// צור את העורך ואת מפעיל הפקודות
editor := &TextEditor{}
invoker := &EditorInvoker{}
for i := 0; i < numOps; i++ {
// קרא את סוג הפעולה
opLine, _ := reader.ReadString('\n')
opType := strings.TrimSpace(opLine)
var result string
switch opType {
case "insert":
// קרא את הטקסט להוספה
textLine, _ := reader.ReadString('\n')
text := strings.TrimSuffix(textLine, "\n")
// TODO: צור InsertCommand והפעל אותו באמצעות מפעיל הפקודות
_ = text
_ = editor
case "delete":
// קרא את מספר התווים למחיקה
countLine, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countLine))
// TODO: צור DeleteCommand והפעל אותו באמצעות מפעיל הפקודות
_ = count
case "undo":
// TODO: קרא ל-UndoLast של מפעיל הפקודות
}
fmt.Println(result)
}
// הדפס את התוכן הסופי
fmt.Printf("Final: %s\n", editor.Content)
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
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 אונליין