שרשור מתודות
חלק מהיחידה תכנות מונחה עצמים במסלול ה-GO של Coddy. שיעור 81 מתוך 107.
שרשור מתודות הוא טכניקה שבה מתודות מחזירות את האובייקט שעליו הן נקראות, וכך מאפשרות לקשר יחד כמה קריאות למתודות במשפט אחד. כבר ראית את זה בתבנית Builder, אבל זהו מושג עצמאי ושימושי בהקשרים רבים.
המפתח הוא להחזיר מצביע למבנה מכל מתודה:
type Query struct {
table string
columns []string
where string
limit int
}
func (q *Query) Select(cols ...string) *Query {
q.columns = cols
return q
}
func (q *Query) From(table string) *Query {
q.table = table
return q
}
func (q *Query) Where(condition string) *Query {
q.where = condition
return q
}
func (q *Query) Limit(n int) *Query {
q.limit = n
return q
}עכשיו אפשר לשרשר את הקריאות האלה בצורה שוטפת:
query := &Query{}
query.Select("name", "email").From("users").Where("active = true").Limit(10)זה נקרא כמעט כמו שפה טבעית בהשוואה למשפטים נפרדים. שרשור מתודות עובד בצורה הטובה ביותר כאשר הפעולות מגדירות או משנות אובייקט בלי צורך להחזיר ערכים אחרים. משתמשים בו לעיתים קרובות בבוני שאילתות, בממשקי API להגדרות ובספריות לבדיקת טענות.
זכור שכל מתודה חייבת להשתמש במקבל מצביע ולהחזיר *StructType כדי שהשרשור יפעל כראוי.
אתגר
קלבואו נבנה בונה של רכיבי HTML בשימוש בשירשור מתודות, כדי ליצור תגי HTML עם מאפיינים ותוכן! זהו יישום מעשי של שירשור מתודות: תיצרו API שכמעט נקרא כמו שפה טבעית בעת בניית רכיבי HTML.
תארגנו את הקוד בשני קבצים:
element.go: הגדירו את בונה רכיבי ה-HTML שלכם עם מתודות שניתן לשרשר.צרו מבנה
Elementששומר את שם התג, מפה של מאפיינים (ממחרוזת למחרוזת) ואת התוכן הפנימי. בנו את המתודות הבאות שניתן לשרשר, וכל אחת מהן מחזירה*Element:NewElement(tag string)- בנאי שיוצר Element חדש עם שם התג הנתון ומפה מאותחלת (ריקה) של מאפייניםAttr(key, value string)- מוסיפה מאפיין לרכיבID(id string)- מתודת נוחות שמגדירה את המאפיין "id"Class(class string)- מתודת נוחות שמגדירה את המאפיין "class"Content(text string)- מגדירה את התוכן הפנימי של הרכיב
הוסיפו מתודת
Render()שמחזירה מחרוזת HTML. המאפיינים צריכים להופיע בסדר אלפביתי לפי המפתח. הפורמט הוא:<tag attr1="value1" attr2="value2">content</tag>אם אין מאפיינים, הציגו פשוט את
<tag>content</tag>. אם אין תוכן, הציגו<tag attr="value"></tag>.main.go: בנו רכיבי HTML באמצעות שירשור מתודות.קראו את שם התג, ואז קראו את מספר הפעולות שיש לבצע. כל פעולה היא אחת מהבאות:
idואחריו ערך ה-IDclassואחריו ערך ה-classattrואחריו שם המאפיין והערך שלו (בשתי שורות)contentואחריו טקסט התוכן
שרשרו את כל הפעולות זו לזו בצורה קולחת, ואז הדפיסו את ה-HTML שנוצר.
הקלטים הבאים יסופקו:
- שורה 1: שם התג
- שורה 2: מספר הפעולות
- השורות הבאות: סוג הפעולה והערכים שלה
לדוגמה, עבור הקלט:
div
3
id
container
class
main-content
content
Hello Worldהפלט שלכם צריך להיות:
<div class="main-content" id="container">Hello World</div>ועבור הקלט:
a
3
attr
href
https://example.com
attr
target
_blank
content
Click hereהפלט שלכם צריך להיות:
<a href="https://example.com" target="_blank">Click here</a>ועבור הקלט:
input
2
attr
type
text
attr
placeholder
Enter nameהפלט שלכם צריך להיות:
<input placeholder="Enter name" type="text"></input>ועבור הקלט:
p
1
content
Simple paragraphהפלט שלכם צריך להיות:
<p>Simple paragraph</p>שימו לב איך שירשור מתודות מאפשר לכם לבנות רכיבים צעד אחר צעד בדרך קריאה. כל מתודה משנה את הרכיב ומחזירה אותו, כך שאפשר לשרשר ישירות את הקריאה למתודה הבאה.
נסו בעצמכם
package main
import (
"bufio"
"fmt"
"os"
"strconv"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// קרא את שם התגית
scanner.Scan()
tagName := scanner.Text()
// קרא את מספר הפעולות
scanner.Scan()
numOps, _ := strconv.Atoi(scanner.Text())
// צור את האלמנט
element := NewElement(tagName)
// עבד כל פעולה
for i := 0; i < numOps; i++ {
scanner.Scan()
operation := scanner.Text()
// TODO: בהתאם לסוג הפעולה, שרשר את המתודה המתאימה
// הפעולות האפשריות הן: "id", "class", "attr" או "content"
// - עבור "id": קרא את השורה הבאה כערך ה-ID
// - עבור "class": קרא את השורה הבאה כערך המחלקה
// - עבור "attr": קרא את TWO השורות הבאות (שם המאפיין, ואז הערך)
// - עבור "content": קרא את השורה הבאה כטקסט התוכן
// השתמש בשרשור מתודות כדי לבנות את האלמנט באופן שוטף
_ = operation // הסר את השורה הזו בעת המימוש
}
// הדפס את ה-HTML שעבר רינדור
fmt.Println(element.Render())
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
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 אונליין