Middleware בתור Decorator
חלק מהיחידה תכנות מונחה עצמים במסלול ה-GO של Coddy. שיעור 97 מתוך 107.
Middleware היא אחת היישומים המעשיים ביותר של תבנית Decorator ב-Go. בפיתוח אתרים, פונקציות middleware עוטפות מטפלי HTTP כדי להוסיף היבטים רוחביים כמו רישום ביומן, אימות או מדידת זמן, בלי לשנות את המטפל המקורי.
התובנה המרכזית היא ש־middleware מקבל מטפל ומחזיר מטפל חדש עם התנהגות נוספת. זה תואם בדיוק לתבנית Decorator: אותו ממשק בכניסה וביציאה, עם פונקציונליות משופרת:
type Handler func(request string) string
func LoggingMiddleware(next Handler) Handler {
return func(request string) string {
result := next(request)
return "[LOG] " + result
}
}
func AuthMiddleware(next Handler) Handler {
return func(request string) string {
return "[AUTH] " + next(request)
}
}כל שכבת ביניים עוטפת את המטפל הבא בשרשרת, ומוסיפה את ההתנהגות שלה לפני הקריאה למטפל העטוף או אחריה. אפשר לערום כמה שכבות ביניים באמצעות הרכבתן:
func MainHandler(request string) string {
return "Response to: " + request
}
handler := LoggingMiddleware(AuthMiddleware(MainHandler))
fmt.Println(handler("GET /users"))
// [LOG] [AUTH] Response to: GET /usersסדר העטיפות קובע את סדר הביצוע: תוכנת הביניים החיצונית ביותר מופעלת ראשונה. נעשה שימוש נרחב בתבנית הזאת במסגרות עבודה של Go לאינטרנט עבור צינורות עיבוד של בקשות, וכך קל להוסיף או להסיר פונקציונליות בלי לגעת בלוגיקה המרכזית של מטפל הבקשות.
אתגר
קלבואו נבנה צינור עיבוד בקשות באמצעות תבנית Middleware כ-Decorator! תיצור מערכת שבה פונקציות middleware עוטפות מטפלים כדי להוסיף פונקציונליות כמו תזמון, אימות ועיצוב — כל זאת בלי לשנות את הלוגיקה של המטפל המרכזי.
תארגן את הקוד שלך בשלושה קבצים:
handler.go: הגדר את סוג המטפל שלך ואת המטפל הבסיסי שמעבד בקשות.צור סוג
Handlerכפונקציה שמקבלת מחרוזת בקשה ומחזירה מחרוזת תגובה:type Handler func(request string) stringממש פונקציה בשם
BaseHandlerשמחזירהProcessed: [request]עבור כל בקשה שהיא מקבלת.middleware.go: צור פונקציות middleware שעוטפות מטפלים בהתנהגות נוספת.בנה שלוש פונקציות middleware, שכל אחת מהן מקבלת
HandlerומחזירהHandlerחדש:TimingMiddleware: עוטפת את התגובה במידע על התזמון ומחזירה[TIMING] [response]ValidationMiddleware: מוסיפה קידומת אימות ומחזירה[VALID] [response]UppercaseMiddleware: ממירה את התגובה כולה לאותיות גדולות
כל middleware צריכה לקרוא למטפל העטוף ולשפר את התוצאה שלו.
main.go: בנה צינור עיבוד באמצעות שילוב של middleware.קרא את מספר שכבות ה-middleware שיש להפעיל. לאחר מכן, עבור כל שכבה, קרא את סוג ה-middleware (
timing,validationאוuppercase) ועטוף את המטפל הנוכחי שלך באותו middleware. החל אותם לפי סדר הקריאה: ה-middleware הראשון שנקרא יהפוך לעוטף החיצוני ביותר.לאחר בניית הצינור, קרא את מחרוזת הבקשה, עבד אותה באמצעות המטפל המשולב שלך והדפס את התוצאה.
הקלטים הבאים יסופקו:
- שורה 1: מספר שכבות ה-middleware
- השורות הבאות: סוג middleware אחד בכל שורה (
timing,validationאוuppercase) - השורה האחרונה: הבקשה לעיבוד
לדוגמה, בהינתן:
2
timing
validation
GET /usersהפלט שלך אמור להיות:
[TIMING] [VALID] Processed: GET /usersובהינתן:
3
uppercase
timing
validation
POST /dataהפלט שלך אמור להיות:
[TIMING] [VALID] PROCESSED: POST /DATAובהינתן:
1
uppercase
hello worldהפלט שלך אמור להיות:
PROCESSED: HELLO WORLDובהינתן:
0
simple requestהפלט שלך אמור להיות:
Processed: simple requestשימו לב כיצד סדר העיטוף של ה-middleware משפיע על הפלט: ה-middleware הראשון שהוחל (החיצוני ביותר) פועל ראשון ורואה את התוצאה הסופית מכל ה-middleware הפנימיים. בדוגמה השנייה, ה-middleware של האותיות הגדולות פועל אחרון (הפנימי ביותר), ולכן הוא ממיר את תגובת הבסיס לאותיות גדולות לפני ש-middleware התזמון והאימות מוסיפות את הקידומות שלהן!
נסו בעצמכם
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// קראו את מספר שכבות ה-middleware
nLine, _ := reader.ReadString('\n')
n, _ := strconv.Atoi(strings.TrimSpace(nLine))
// קראו את סוגי ה-middleware
middlewareTypes := make([]string, n)
for i := 0; i < n; i++ {
line, _ := reader.ReadString('\n')
middlewareTypes[i] = strings.TrimSpace(line)
}
// קראו את הבקשה
request, _ := reader.ReadString('\n')
request = strings.TrimSpace(request)
// TODO: התחילו עם BaseHandler
// TODO: בנו את ה-pipeline על ידי עטיפת ה-handler ב-middleware
// החילו את ה-middleware לפי הסדר - ה-middleware הראשון שנקרא הופך לעטיפה החיצונית ביותר
// רמז: עברו על middlewareTypes בסדר הפוך כדי להשיג זאת
// ה-middleware האחרון שיוחל יהיה החיצוני ביותר (ויפעל ראשון)
// TODO: עבדו את הבקשה באמצעות ה-handler המורכב והדפיסו את התוצאה
fmt.Println("TODO: implement pipeline")
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
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 אונליין