תבנית Strategy
חלק מהיחידה תכנות מונחה עצמים במסלול ה-GO של Coddy. שיעור 90 מתוך 107.
תבנית Strategy מאפשרת לך להגדיר משפחה של אלגוריתמים, לעטוף כל אחד מהם, ולאפשר להחליף ביניהם בזמן ריצה. בניגוד לתבנית Observer שבה כמה אובייקטים מגיבים לאירוע אחד, Strategy מאפשרת לאובייקט יחיד לעבור באופן דינמי בין התנהגויות שונות.
ב-Go, מממשים את Strategy באמצעות הגדרת ממשק עבור האלגוריתם והזרקת מימושים שונים למבנה הקשר:
type PaymentStrategy interface {
Pay(amount float64) string
}
type CreditCard struct{}
func (c CreditCard) Pay(amount float64) string {
return fmt.Sprintf("Paid %.2f via Credit Card", amount)
}
type PayPal struct{}
func (p PayPal) Pay(amount float64) string {
return fmt.Sprintf("Paid %.2f via PayPal", amount)
}מבנה ההקשר מכיל הפניה לממשק האסטרטגיה, וכך ניתן להחליף את האלגוריתם בלי לשנות את הקוד של ההקשר:
type Checkout struct {
strategy PaymentStrategy
}
func (c *Checkout) SetStrategy(s PaymentStrategy) {
c.strategy = s
}
func (c *Checkout) ProcessPayment(amount float64) string {
return c.strategy.Pay(amount)
}עכשיו אפשר לשנות את שיטת התשלום בזמן ריצה:
checkout := &Checkout{}
checkout.SetStrategy(CreditCard{})
fmt.Println(checkout.ProcessPayment(100.00)) // שולמו 100.00 באמצעות כרטיס אשראי
checkout.SetStrategy(PayPal{})
fmt.Println(checkout.ProcessPayment(50.00)) // שולמו 50.00 באמצעות PayPalתבנית האסטרטגיה מתאימה במיוחד כשיש לך כמה דרכים לבצע פעולה ועליך לבחור אחת מהן בהתאם לקלט מהמשתמש, לתצורה או לתנאים בזמן ריצה. מקרי שימוש נפוצים כוללים אלגוריתמי מיון, שיטות דחיסה וכללי אימות.
אתגר
קלבואו נבנה מערכת לעיצוב טקסט באמצעות תבנית Strategy! תיצרו אסטרטגיות עיצוב שונות שאפשר להחליף בזמן ריצה, כך שאותו מעבד טקסט יוכל להפיק סגנונות פלט שונים בלי לשנות את קוד הליבה שלו.
תארגנו את הקוד בשלושה קבצים:
formatter.go: הגדירו את ממשק האסטרטגיה ואת אסטרטגיות העיצוב המוחשיות.צרו ממשק
TextFormatterעם מתודהFormat(text string) stringשמשנה את הטקסט בהתאם לכללי האסטרטגיה.ממשו שלוש אסטרטגיות עיצוב:
UppercaseFormatter: ממיר את כל הטקסט לאותיות גדולותSnakeCaseFormatter: ממיר רווחים לקווים תחתונים והופך את הטקסט לאותיות קטנותTitleFormatter: הופך את האות הראשונה בכל מילה לאות גדולה (מילים מופרדות ברווחים)
processor.go: צרו את מבנה ההקשר שמשתמש באסטרטגיית העיצוב.בנו מבנה
TextProcessorשמכיל אסטרטגייתTextFormatter. הוסיפו את המתודות הבאות:SetFormatter(f TextFormatter)כדי לשנות את אסטרטגיית העיצוב הפעילהProcess(text string) stringשמחילה על הטקסט את המעצב הנוכחי
צרו גם בנאי
NewTextProcessor()שמחזיר מעבד ללא מעצב ראשוני מוגדר.main.go: הדגימו החלפת אסטרטגיות בזמן ריצה.קראו קטע טקסט, ולאחר מכן קראו את מספר פעולות העיצוב. עבור כל פעולה, קראו את סוג העיצוב (
upper,snakeאוtitle), הגדירו במעבד את המעצב המתאים, עבדו את הטקסט המקורי והדפיסו את התוצאה.
הקלטים הבאים יסופקו:
- שורה 1: הטקסט לעיצוב
- שורה 2: מספר פעולות העיצוב
- השורות הבאות: סוג העיצוב עבור כל פעולה
לדוגמה, בהינתן:
hello world today
3
upper
snake
titleהפלט שלכם צריך להיות:
HELLO WORLD TODAY
hello_world_today
Hello World Todayובהינתן:
Go Programming Language
2
snake
upperהפלט שלכם צריך להיות:
go_programming_language
GO PROGRAMMING LANGUAGEובהינתן:
design patterns are useful
1
titleהפלט שלכם צריך להיות:
Design Patterns Are Usefulשימו לב שאותו TextProcessor מפיק פלטים שונים לחלוטין רק על ידי החלפת אסטרטגיית המעצב שלו. למעבד לא ידוע ולא אכפת באיזה מעצב מסוים הוא משתמש. הוא פשוט מעביר את העבודה לאסטרטגיה שהוגדרה כרגע!
נסו בעצמכם
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// קרא את הטקסט שיש לעצב
text, _ := reader.ReadString('\n')
text = strings.TrimSpace(text)
// קרא את מספר הפעולות
countStr, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countStr))
// צור מעבד טקסט חדש
processor := NewTextProcessor()
// בצע כל פעולת עיצוב
for i := 0; i < count; i++ {
formatType, _ := reader.ReadString('\n')
formatType = strings.TrimSpace(formatType)
// TODO: בהתאם ל-formatType ("upper", "snake" או "title"),
// הגדר במעבד את מעצב הטקסט המתאים
// והדפס את הטקסט המעובד
}
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות OOP ב-Go
קבצים חיצונייםסביבת העבודה והמודולים של Goחבילות וייבואשמות מיוצאים לעומת שמות לא מיוצאיםמבוא ל-OOP ב-GoStructs כמחלקותהגדרת מתודות עבור Structsמקבלי Pointer לעומת Valueאתחול Structפונקציות Constructorחזרה – מחשבון פשוט4ממשקים
מבוא לממשקיםמימוש משתמעממשק כחוזהממשק ריק (any)אימות טיפוסמתג טיפוסיםהרכבת ממשקיםממשקי Stringer ו-Errorסיכום – מחשבון צורות7כימוס
שדות מיוצאים לעומת שדות לא מיוצאיםכימוס ברמת החבילהמתודות Getter ו-Setterהסתרת מידע ב-Goסיכום – רשומות תלמידים10Generics (Go 1.18 ומעלה)
מבוא ל־Genericsפרמטרים של טיפוסיםאילוצי טיפוסיםמבנים גנרייםפתרון עוקף למתודות גנריותסיכום – אוסף גנרי13תבניות עיצוב חלק 1
מבוא לתבניות עיצובתבנית Singletonתבנית Factoryתבנית Abstract Factoryתבנית Observerתבנית Strategy2העמקה בסוגים וב-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 API3מצביעים וזיכרון
יסודות המצביעים ב-Goמצביעים למבניםהעברה לפי ערך לעומת לפי הפניההפונקציה new()איסוף אשפה ב-Goסיכום - בונה רשימה מקושרת6פולימורפיזם ב-Go
פולימורפיזם באמצעות ממשקיםטיפוס ברווז ב-Goכללי התאמה לממשקאוספים פולימורפייםהזרקת תלויותסיכום - מעבד תשלומים9מקביליות ו-OOP
יסודות GoroutinesChannels ותקשורתChannel עם חוצץ לעומת ללא חוצץפקודת Selectsync.Mutex ו-sync.RWMutexsync.WaitGroupתכנון Struct בטוח לשימוש במקבילסיכום - מאגר עובדים12מושגים מתקדמים בתכנות מונחה עצמים
דפוס האפשרויות הפונקציונליותדפוס Builder ב-Goשרשור מתודותכינויי טיפוסים לעומת הגדרותיסודות Reflectionסקירה כללית של יצירת קודתרגלו בעצמכם: קומפיילר Go אונליין