Menu
Coddy logo textTech

תבנית הפקודה

חלק מהיחידה תכנות מונחה עצמים במסלול ה-GO של Coddy. שיעור 91 מתוך 107.

דפוס Command עוטף בקשה כאובייקט, ומאפשר לך להעביר פעולות כפרמטרים, להכניס אותן לתור או לתמוך בפונקציונליות של ביטול. בעוד ש-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 אידיאלי למימוש מערכות לביטול ולביצוע מחדש, תורי משימות או הקלטת פקודות מאקרו, במקרים שבהם צריך לאחסן, לדחות או להפעיל פעולות מחדש.

challenge icon

אתגר

קל

בואו נבנה עורך טקסט עם פונקציונליות של ביטול באמצעות תבנית 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)
}
quiz iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה תכנות מונחה עצמים

תרגלו בעצמכם: קומפיילר Go אונליין