דפוס האפשרויות הפונקציונליות
חלק מהיחידה תכנות מונחה עצמים במסלול ה-GO של Coddy. שיעור 79 מתוך 107.
כשיוצרים מבנים עם שדות תצורה אופציונליים רבים, פונקציות בנייה עלולות להפוך למסורבלות. תבנית האפשרויות הפונקציונליות פותרת זאת באלגנטיות באמצעות פונקציות להגדרת אובייקטים.
נבחן struct של שרת עם מספר הגדרות אופציונליות. במקום להשתמש בבנאי עם פרמטרים רבים, נגדיר טיפוס אפשרות ופונקציות שמחזירות אפשרויות:
type Server struct {
host string
port int
timeout int
}
type Option func(*Server)
func WithPort(port int) Option {
return func(s *Server) {
s.port = port
}
}
func WithTimeout(timeout int) Option {
return func(s *Server) {
s.timeout = timeout
}
}הבנאי מקבל רשימה וריאדית של אפשרויות ומחיל כל אחת מהן:
func NewServer(host string, opts ...Option) *Server {
s := &Server{
host: host,
port: 8080, // ברירת מחדל
timeout: 30, // ברירת מחדל
}
for _, opt := range opts {
opt(s)
}
return s
}כעת יצירת שרתים נעשית קריאה וגמישה:
// השתמשו בהגדרות ברירת המחדל
s1 := NewServer("localhost")
// התאימו אישית אפשרויות מסוימות
s2 := NewServer("localhost", WithPort(3000), WithTimeout(60))הדפוס הזה מצטיין בכך שהמתקשרים מציינים רק את מה שהם צריכים, ברירות המחדל ברורות במקום אחד, והוספת אפשרויות חדשות אינה שוברת קוד קיים. הוא נמצא בשימוש נרחב בספריות Go כמו grpc ו-zap.
אתגר
קלבואו נבנה חיבור למסד נתונים שניתן להגדיר באמצעות תבנית האפשרויות הפונקציונליות! התבנית הזאת מצטיינת כשיש הגדרות אופציונליות רבות עם ערכי ברירת מחדל הגיוניים — בדיוק מה שנדרש לחיבור למסד נתונים.
תארגנו את הקוד בשני קבצים:
database.go: הגדירו את טיפוס חיבור מסד הנתונים ואת פונקציות האפשרויות שלכם.צרו מבנה
DBConnectionעם השדות הלא־מיוצאים הבאים:host(מחרוזת)port(מספר שלם)username(מחרוזת)password(מחרוזת)maxConnections(מספר שלם)timeout(מספר שלם) - בשניות
הגדירו טיפוס
Optionכפונקציה שמשנה*DBConnection.צרו את פונקציות האפשרויות הבאות, שמחזירות
Option:WithPort(port int)WithCredentials(username, password string)WithMaxConnections(max int)WithTimeout(seconds int)
צרו בנאי
NewDBConnection(host string, opts ...Option)שמחזיר*DBConnection. הגדירו את ברירות המחדל הבאות: port5432, username"admin", password"secret", maxConnections10, timeout30. החילו את כל האפשרויות שסופקו לאחר הגדרת ברירות המחדל.הוסיפו מתודה
ConnectionString()שמחזירה את פרטי החיבור בפורמט הזה:[username]:[password]@[host]:[port] (max:[maxConnections], timeout:[timeout]s)main.go: בנו חיבורי מסד נתונים עם הגדרות שונות.קראו את שם המארח, ואז קראו את מספר האפשרויות להחלה. עבור כל אפשרות, קראו את סוג האפשרות ואת הערך או הערכים שלה:
portואחריו מספר הפורטcredentialsואחריו שם המשתמש והסיסמה (שתי שורות)maxconnואחריו מספר החיבורים המרביtimeoutואחריו הזמן הקצוב בשניות
צרו
DBConnectionעם האפשרויות שצוינו והדפיסו את מחרוזת החיבור שלו.
הקלטים הבאים יסופקו:
- שורה 1: שם המארח
- שורה 2: מספר האפשרויות
- השורות הבאות: סוג האפשרות והערך או הערכים שלה
לדוגמה, עבור הקלט:
localhost
0הפלט שלכם צריך להיות:
admin:secret@localhost:5432 (max:10, timeout:30s)ועבור הקלט:
db.example.com
2
port
3306
credentials
root
mypasswordהפלט שלכם צריך להיות:
root:mypassword@db.example.com:3306 (max:10, timeout:30s)ועבור הקלט:
production.server
4
port
5433
credentials
dbuser
securepass123
maxconn
50
timeout
60הפלט שלכם צריך להיות:
dbuser:securepass123@production.server:5433 (max:50, timeout:60s)שימו לב שהקוראים מציינים רק את האפשרויות שנחוצות להם: הגדרות שלא צוינו משתמשות בערכי ברירת מחדל הגיוניים. זהו היופי שבתבנית האפשרויות הפונקציונליות!
נסו בעצמכם
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// קרא את host
host, _ := reader.ReadString('\n')
host = strings.TrimSpace(host)
// קרא את מספר האפשרויות
numOptionsStr, _ := reader.ReadString('\n')
numOptions, _ := strconv.Atoi(strings.TrimSpace(numOptionsStr))
// אסוף אפשרויות
var opts []Option
for i := 0; i < numOptions; i++ {
optionType, _ := reader.ReadString('\n')
optionType = strings.TrimSpace(optionType)
switch optionType {
case "port":
portStr, _ := reader.ReadString('\n')
port, _ := strconv.Atoi(strings.TrimSpace(portStr))
// TODO: הוסף את האפשרות WithPort ל-opts
_ = port // הסר שורה זו בעת המימוש
case "credentials":
username, _ := reader.ReadString('\n')
username = strings.TrimSpace(username)
password, _ := reader.ReadString('\n')
password = strings.TrimSpace(password)
// TODO: הוסף את האפשרות WithCredentials ל-opts
_, _ = username, password // הסר שורה זו בעת המימוש
case "maxconn":
maxStr, _ := reader.ReadString('\n')
maxConn, _ := strconv.Atoi(strings.TrimSpace(maxStr))
// TODO: הוסף את האפשרות WithMaxConnections ל-opts
_ = maxConn // הסר שורה זו בעת המימוש
case "timeout":
timeoutStr, _ := reader.ReadString('\n')
timeout, _ := strconv.Atoi(strings.TrimSpace(timeoutStr))
// TODO: הוסף את האפשרות WithTimeout ל-opts
_ = timeout // הסר שורה זו בעת המימוש
}
}
// TODO: צור DBConnection באמצעות NewDBConnection עם host ו-opts
// TODO: הדפס את מחרוזת החיבור באמצעות המתודה ConnectionString()
_ = opts // הסר שורה זו בעת המימוש
fmt.Println("TODO: Print connection string here")
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
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 API3מצביעים וזיכרון
יסודות המצביעים ב-Goמצביעים למבניםהעברה לפי ערך לעומת לפי הפניההפונקציה new()איסוף אשפה ב-Goסיכום - בונה רשימה מקושרת6פולימורפיזם ב-Go
פולימורפיזם באמצעות ממשקיםטיפוס ברווז ב-Goכללי התאמה לממשקאוספים פולימורפייםהזרקת תלויותסיכום - מעבד תשלומים9מקביליות ו-OOP
יסודות GoroutinesChannels ותקשורתChannel עם חוצץ לעומת ללא חוצץפקודת Selectsync.Mutex ו-sync.RWMutexsync.WaitGroupתכנון Struct בטוח לשימוש במקבילסיכום - מאגר עובדים12מושגים מתקדמים בתכנות מונחה עצמים
דפוס האפשרויות הפונקציונליותדפוס Builder ב-Goשרשור מתודותכינויי טיפוסים לעומת הגדרותיסודות Reflectionסקירה כללית של יצירת קודתרגלו בעצמכם: קומפיילר Go אונליין