דפוס Builder ב-Go
חלק מהיחידה תכנות מונחה עצמים במסלול ה-GO של Coddy. שיעור 80 מתוך 107.
תבנית הבנאי מפרידה בין בניית אובייקט מורכב לבין הייצוג שלו. בניגוד לתבנית האפשרויות הפונקציונליות, תבנית הבנאי משתמשת במבנה ייעודי לבנאי עם מתודות שמגדירות את האובייקט שלב אחר שלב.
כך מיישמים builder עבור בקשת HTTP:
type Request struct {
method string
url string
headers map[string]string
body string
}
type RequestBuilder struct {
request *Request
}
func NewRequestBuilder() *RequestBuilder {
return &RequestBuilder{
request: &Request{
headers: make(map[string]string),
},
}
}
func (b *RequestBuilder) Method(m string) *RequestBuilder {
b.request.method = m
return b
}
func (b *RequestBuilder) URL(u string) *RequestBuilder {
b.request.url = u
return b
}
func (b *RequestBuilder) Header(key, value string) *RequestBuilder {
b.request.headers[key] = value
return b
}
func (b *RequestBuilder) Build() *Request {
return b.request
}כל מתודה מחזירה את מצביע הבונה, ומאפשרת לשרשר מתודות. המתודה Build() מסיימת את הבנייה ומחזירה את האובייקט שנוצר:
req := NewRequestBuilder().
Method("POST").
URL("https://api.example.com").
Header("Content-Type", "application/json").
Build()תבנית Builder מצטיינת כשאובייקטים דורשים כמה שלבי הגדרה או כשצריך לאכוף סדר בנייה מסוים. היא שימושית במיוחד ליצירת אובייקטים בלתי ניתנים לשינוי, או כשהתהליך עצמו של הבנייה דורש אימות.
אתגר
קלבואו נבנה רכיב ליצירת הודעות דוא״ל באמצעות תבנית הבנאי (Builder Pattern)! להודעות דוא״ל יש רכיבים אופציונליים רבים — נמענים, עותק (CC), נושא, גוף וקבצים מצורפים — ולכן הן מתאימות במיוחד ליצירה שלב אחר שלב באמצעות שרשור מתודות.
תארגן את הקוד שלך בשני קבצים:
email.go: הגדר את מבנה הנתונים של הודעת הדוא״ל שלך ואת הבנאי שלה.צור מבנה
Emailעם השדות האלה:from(מחרוזת)to(פרוסה של מחרוזות)cc(פרוסה של מחרוזות)subject(מחרוזת)body(מחרוזת)
צור מבנה
EmailBuilderשמחזיק מצביע ל-Email שנבנה. ממש אתNewEmailBuilder(), שמחזירה בנאי חדש עם Email מאותחל (פרוסות ריקות עבורtoו-cc).הוסף את מתודות הבנאי האלה, שכל אחת מהן מחזירה
*EmailBuilderכדי לאפשר שרשור:From(address string)- מגדירה את כתובת השולחTo(address string)- מוסיפה נמען לפרוסתtoCC(address string)- מוסיפה נמען לפרוסתccSubject(subject string)- מגדירה את שורת הנושאBody(body string)- מגדירה את גוף הודעת הדוא״ל
הוסף מתודת
Build()שמחזירה את*Emailשנבנה.לבסוף, הוסף למבנה Email מתודת
Summary()שמחזירה מחרוזת בתבנית הזאת:From: [from] To: [comma-separated to addresses] CC: [comma-separated cc addresses or "none"] Subject: [subject] Body: [body]main.go: בנה והצג הודעות דוא״ל באמצעות הבנאי.קרא את כתובת השולח, ואז קרא את מספר הנמענים ואת כתובת הדוא״ל של כל נמען. לאחר מכן, קרא את מספר הנמענים שמקבלים עותק (CC) ואת הכתובת של כל אחד מהם. לבסוף, קרא את הנושא ואת גוף ההודעה.
השתמש ב-EmailBuilder עם שרשור מתודות כדי לבנות את הודעת הדוא״ל, ואז הדפס את הסיכום שלה.
הקלטים הבאים יסופקו:
- שורה 1: כתובת השולח
- שורה 2: מספר הנמענים
- השורות הבאות: כתובת הדוא״ל של כל נמען
- השורה הבאה: מספר הנמענים שמקבלים עותק (CC)
- השורות הבאות: הכתובת של כל נמען שמקבל עותק (CC)
- השורה הבאה: נושא
- השורה האחרונה: גוף ההודעה
לדוגמה, בהינתן:
alice@company.com
2
bob@company.com
carol@company.com
1
manager@company.com
Weekly Report
Please find the weekly report attached.הפלט שלך צריך להיות:
From: alice@company.com
To: bob@company.com, carol@company.com
CC: manager@company.com
Subject: Weekly Report
Body: Please find the weekly report attached.ובהינתן:
support@service.com
1
customer@email.com
0
Your Ticket Update
Your support ticket has been resolved.הפלט שלך צריך להיות:
From: support@service.com
To: customer@email.com
CC: none
Subject: Your Ticket Update
Body: Your support ticket has been resolved.שים לב איך הבנאי מאפשר לך להוסיף כמה נמענים, אחד בכל פעם, באמצעות קריאות חוזרות ל-To(), ואיך התבנית שומרת על תהליך בנייה נקי וקריא גם כשיש שדות אופציונליים רבים.
נסו בעצמכם
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// קרא את כתובת השולח
from, _ := reader.ReadString('\n')
from = strings.TrimSpace(from)
// קרא את מספר הנמענים
toCountStr, _ := reader.ReadString('\n')
toCount, _ := strconv.Atoi(strings.TrimSpace(toCountStr))
// קרא את הכתובת של כל נמען
toAddresses := make([]string, toCount)
for i := 0; i < toCount; i++ {
addr, _ := reader.ReadString('\n')
toAddresses[i] = strings.TrimSpace(addr)
}
// קרא את מספר הנמענים בעותק (CC)
ccCountStr, _ := reader.ReadString('\n')
ccCount, _ := strconv.Atoi(strings.TrimSpace(ccCountStr))
// קרא כל כתובת בעותק (CC)
ccAddresses := make([]string, ccCount)
for i := 0; i < ccCount; i++ {
addr, _ := reader.ReadString('\n')
ccAddresses[i] = strings.TrimSpace(addr)
}
// קרא את הנושא
subject, _ := reader.ReadString('\n')
subject = strings.TrimSpace(subject)
// קרא את גוף ההודעה
body, _ := reader.ReadString('\n')
body = strings.TrimSpace(body)
// TODO: השתמש ב-EmailBuilder עם שרשור מתודות כדי לבנות את הודעת הדוא״ל
// התחל עם NewEmailBuilder(), ואז שרשר את From(), To(), CC(), Subject(), Body()
// לבסוף, קרא ל-Build() כדי לקבל את ה-Email
// TODO: הדפס את סיכום הודעת הדוא״ל באמצעות המתודה Summary()
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
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 אונליין