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