Menu
Coddy logo textTech

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 לאינטרנט עבור צינורות עיבוד של בקשות, וכך קל להוסיף או להסיר פונקציונליות בלי לגעת בלוגיקה המרכזית של מטפל הבקשות.

challenge icon

אתגר

קל

בואו נבנה צינור עיבוד בקשות באמצעות תבנית 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")
}
quiz iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה תכנות מונחה עצמים

תרגלו בעצמכם: קומפיילר Go אונליין