Menu
Coddy logo textTech

תבנית שיטת התבנית

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

תבנית Template Method מגדירה את השלד של אלגוריתם בטיפוס בסיסי, ומאפשרת לתת־טיפוסים לדרוס שלבים מסוימים בלי לשנות את המבנה הכולל. בעוד שתבנית Decorator מוסיפה התנהגות באמצעות עטיפת אובייקטים, תבנית Template Method שולטת בזרימת האלגוריתם ומאפשרת להתאים אישית שלבים בודדים.

ב-Go, מכיוון שאין ירושה, אנחנו מממשים את התבנית הזו באמצעות הטמעת מבנים (struct embedding) בשילוב עם ממשקים. מבנה ה״תבנית״ מגדיר את מבנה האלגוריתם וקורא למתודות שניתן להתאים אישית:

type DataProcessor interface {
    ReadData() string
    ProcessData(data string) string
    SaveData(result string) string
}

type BaseProcessor struct {
    Impl DataProcessor
}

func (b *BaseProcessor) Execute() string {
    data := b.Impl.ReadData()
    result := b.Impl.ProcessData(data)
    return b.Impl.SaveData(result)
}

מימושים קונקרטיים מספקים גרסאות משלהם לכל שלב, בעוד זרימת האלגוריתם נשארת ללא שינוי:

type CSVProcessor struct{}

func (c CSVProcessor) ReadData() string       { return "csv-data" }
func (c CSVProcessor) ProcessData(d string) string { return "processed-" + d }
func (c CSVProcessor) SaveData(r string) string    { return "Saved: " + r }

type JSONProcessor struct{}

func (j JSONProcessor) ReadData() string       { return "json-data" }
func (j JSONProcessor) ProcessData(d string) string { return "parsed-" + d }
func (j JSONProcessor) SaveData(r string) string    { return "Stored: " + r }

שיטת התבנית Execute מתזמרת את השלבים בסדר קבוע:

csvProc := &BaseProcessor{Impl: CSVProcessor{}}
fmt.Println(csvProc.Execute())  // נשמר: processed-csv-data

jsonProc := &BaseProcessor{Impl: JSONProcessor{}}
fmt.Println(jsonProc.Execute()) // אוחסן: parsed-json-data

תבנית המתודה מתאימה במיוחד כשיש לך אלגוריתמים בעלי מבנה זהה אך שלבים מסוימים בהם שונים, למשל צינורות לייבוא ולייצוא נתונים, מחוללי דוחות או מסגרות לבדיקות.

challenge icon

אתגר

קל

בוא נבנה מערכת ליצירת דוחות באמצעות תבנית Template Method! תיצור מסגרת שבה סוגי דוחות שונים פועלים לפי אותו תהליך יצירה: איסוף נתונים, עיצובם והפקת התוצאה, אבל כל סוג דוח מתאים את השלבים האלה באופן שונה.

תארגן את הקוד שלך בשלושה קבצים:

  • report.go: הגדר את הממשק שלך ואת המעבד הבסיסי שמתזמר את האלגוריתם ליצירת הדוח.

    צור ממשק ReportGenerator עם שלוש מתודות שמייצגות את השלבים ביצירת דוח:

    • GatherData() string: מאחזרת את הנתונים הגולמיים עבור הדוח
    • FormatData(data string) string: מעבדת את הנתונים לפורמט של הדוח
    • OutputReport(formatted string) string: מפיקה את הודעת הפלט הסופית

    בנה מבנה ReportProcessor שמכיל מימוש של ReportGenerator. הוסף מתודה Generate() string שמבצעת את שלושת השלבים לפי הסדר: איסוף, עיצוב ואז הפקה, ומחזירה את התוצאה הסופית.

  • generators.go: ממש את מחוללי הדוחות הקונקרטיים שמתאימים כל שלב.

    צור שני סוגי דוחות:

    • SalesReport עם שדה Region (מחרוזת)
      • GatherData() מחזירה sales-data-[region]
      • FormatData(data) מחזירה SALES REPORT: [data]
      • OutputReport(formatted) מחזירה Printed: [formatted]
    • InventoryReport עם שדה Warehouse (מחרוזת)
      • GatherData() מחזירה inventory-[warehouse]
      • FormatData(data) מחזירה *** [data] ***
      • OutputReport(formatted) מחזירה Exported: [formatted]
  • main.go: הדגם כיצד אותו מבנה אלגוריתם מפיק תוצאות שונות בהתאם למימוש.

    קרא את סוג הדוח (sales או inventory) ואת ערך התצורה (אזור עבור sales, מחסן עבור inventory). צור את המחולל המתאים, עטוף אותו ב-ReportProcessor, קרא ל-Generate() והדפס את התוצאה.

הקלטים הבאים יינתנו:

  • שורה 1: סוג הדוח (sales או inventory)
  • שורה 2: ערך התצורה (שם האזור או שם המחסן)

לדוגמה, עבור:

sales
North

הפלט שלך צריך להיות:

Printed: SALES REPORT: sales-data-North

ועבור:

inventory
MainHub

הפלט שלך צריך להיות:

Exported: *** inventory-MainHub ***

ועבור:

sales
West

הפלט שלך צריך להיות:

Printed: SALES REPORT: sales-data-West

שים לב ש-ReportProcessor תמיד קורא לאותם שלושה שלבים באותו הסדר, אבל כל סוג דוח מספק מימוש משלו לשלבים האלה. שלד האלגוריתם נשאר קבוע, בעוד שהפרטים משתנים: כך פועלת תבנית Template Method!

נסו בעצמכם

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// קרא את סוג הדוח
	scanner.Scan()
	reportType := scanner.Text()
	
	// קרא את ערך התצורה (אזור או מחסן)
	scanner.Scan()
	configValue := scanner.Text()

	// TODO: צור את המחולל המתאים לפי reportType
	// - אם reportType הוא "sales", צור SalesReport שבו Region מוגדר ל-configValue
	// - אם reportType הוא "inventory", צור InventoryReport שבו Warehouse מוגדר ל-configValue
	
	// TODO: צור ReportProcessor עם המחולל
	
	// TODO: קרא ל-Generate() והדפס את התוצאה
	_ = reportType
	_ = configValue
	fmt.Println("TODO: Generate and print the report")
}
quiz iconבחנו את עצמכם

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

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

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