Menu
Coddy logo textTech

תבנית Observer

חלק מהיחידה תכנות מונחה עצמים במסלול ה-GO של Coddy. שיעור 89 מתוך 107.

תבנית Observer מגדירה קשר של אחד לרבים בין אובייקטים. כאשר אובייקט אחד (הנושא) משנה מצב, כל התלויים בו (המשקיפים) מקבלים הודעה באופן אוטומטי. התבנית מתאימה במיוחד למערכות אירועים, להתראות או לכל תרחיש שבו כמה רכיבים צריכים להגיב לשינויים.

ב-Go, אנחנו מממשים את התבנית הזו באמצעות ממשק עבור צופים ומבנה subject שמנהל הרשמות:

type Observer interface {
    Update(event string)
}

type Subject struct {
    observers []Observer
}

func (s *Subject) Subscribe(o Observer) {
    s.observers = append(s.observers, o)
}

func (s *Subject) Notify(event string) {
    for _, observer := range s.observers {
        observer.Update(event)
    }
}

כל טיפוס שמממש את הממשק Observer יכול להירשם כדי לקבל התראות:

type EmailAlert struct{ Address string }

func (e EmailAlert) Update(event string) {
    fmt.Printf("Email to %s: %s\n", e.Address, event)
}

type Logger struct{}

func (l Logger) Update(event string) {
    fmt.Println("Log:", event)
}

הנושא מודיע לכל הצופים בלי לדעת מהם הטיפוסים הקונקרטיים שלהם:

subject := &Subject{}
subject.Subscribe(EmailAlert{Address: "user@example.com"})
subject.Subscribe(Logger{})

subject.Notify("Order placed")
// אימייל אל user@example.com: ההזמנה בוצעה
// יומן: ההזמנה בוצעה

תבנית זו מנתקת את הנושא מהמשקיפים שלו, וכך מקלה על הוספת סוגים חדשים של משקיפים בלי לשנות קוד קיים. משתמשים בה לעיתים קרובות בארכיטקטורות מונחות אירועים, במסגרות עבודה של ממשקי משתמש גרפיים ובמערכות העברת הודעות מסוג פרסום/הרשמה.

challenge icon

אתגר

קל

בואו נבנה מערכת להתראות על מחירי מניות באמצעות תבנית Observer! תיצרו מערכת שבה כמה שירותי התראות מקבלים באופן אוטומטי הודעות בכל פעם שמחיר מניה משתנה — מושלם להדגמה של האופן שבו משקיפים יכולים להגיב לאירועים בלי שהנושא יכיר את הטיפוסים הקונקרטיים שלהם.

תארגנו את הקוד שלכם בשלושה קבצים:

  • observer.go: הגדירו את ממשק המשקיף ואת טיפוסי המשקיפים הקונקרטיים שלכם.

    צרו ממשק Observer עם מתודה Update(stockName string, price float64) שמקבלת מידע על המניה כאשר המחירים משתנים.

    ממשו שני טיפוסי משקיפים:

    • PriceAlert עם שדה Threshold (float64); בעת עדכון, הוא מחזיר ALERT: [stockName] at $[price] crossed threshold $[threshold] אם המחיר גבוה מסף ההתראה, או WATCHING: [stockName] at $[price] (threshold: $[threshold]) אם הוא נמוך ממנו
    • PriceLogger עם שדה LogName (string); בעת עדכון, הוא מחזיר [logName] recorded [stockName]: $[price]

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

  • stock.go: צרו את הנושא שלכם, שמנהל את המשקיפים ומודיע להם על שינויים במחירים.

    בנו מבנה Stock עם שדות עבור Name (string), Price (float64) ופרוסה של משקיפים. הוסיפו את המתודות האלה:

    • Subscribe(o Observer) כדי להוסיף משקיף
    • SetPrice(price float64) []string שמעדכנת את המחיר ומודיעה לכל המשקיפים, ומחזירה פרוסה של כל התגובות שלהם

    צרו גם בנאי NewStock(name string, initialPrice float64) *Stock.

  • main.go: הגדירו את המניה שלכם עם משקיפים ודמו שינויים במחיר.

    קראו את שם המניה ואת המחיר ההתחלתי. לאחר מכן קראו את מספר המשקיפים שיש ליצור. עבור כל משקיף, קראו את הטיפוס שלו (alert או logger) ואת ההגדרה שלו (סף ההתראה עבור התראות, ושם היומן עבור רישומים). רשמו כל משקיף למניה.

    לבסוף, קראו מחיר חדש, עדכנו את המניה והדפיסו את התגובה של כל משקיף בשורה נפרדת.

הקלטים הבאים יסופקו:

  • שורה 1: שם המניה
  • שורה 2: מחיר התחלתי
  • שורה 3: מספר המשקיפים
  • עבור כל משקיף: סוג (alert או logger), ולאחר מכן ערך ההגדרה
  • השורה האחרונה: המחיר החדש שיש לקבוע

עצבו את כל המחירים עם שתי ספרות אחרי הנקודה העשרונית.

לדוגמה, בהינתן:

GOOG
150.00
3
alert
155.00
logger
TradeLog
alert
140.00
160.50

הפלט שלכם אמור להיות:

ALERT: GOOG at $160.50 crossed threshold $155.00
TradeLog recorded GOOG: $160.50
ALERT: GOOG at $160.50 crossed threshold $140.00

ובהינתן:

AAPL
180.00
2
logger
DailyLog
alert
200.00
175.25

הפלט שלכם אמור להיות:

DailyLog recorded AAPL: $175.25
WATCHING: AAPL at $175.25 (threshold: $200.00)

שימו לב שה-Stock לא יודע אם הוא מודיע להתראות או לרישומים. הוא פשוט קורא ל-Update אצל כל משקיף. הניתוק הזה מקל על הוספת טיפוסי משקיפים חדשים בלי לשנות את קוד ה-Stock!

נסו בעצמכם

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// קרא את שם המניה
	stockName, _ := reader.ReadString('\n')
	stockName = strings.TrimSpace(stockName)

	// קרא את המחיר ההתחלתי
	initialPriceStr, _ := reader.ReadString('\n')
	initialPrice, _ := strconv.ParseFloat(strings.TrimSpace(initialPriceStr), 64)

	// קרא את מספר המשקיפים
	numObserversStr, _ := reader.ReadString('\n')
	numObservers, _ := strconv.Atoi(strings.TrimSpace(numObserversStr))

	// TODO: צור Stock חדש באמצעות הבנאי NewStock

	// TODO: עבור על כל משקיף
	for i := 0; i < numObservers; i++ {
		// קרא את סוג המשקיף ("alert" או "logger")
		observerType, _ := reader.ReadString('\n')
		observerType = strings.TrimSpace(observerType)

		// קרא את ערך התצורה
		configValue, _ := reader.ReadString('\n')
		configValue = strings.TrimSpace(configValue)

		// TODO: צור משקיף מתאים לפי הסוג
		// - עבור "alert": המר את configValue לסף מסוג float64, צור PriceAlert
		// - עבור "logger": השתמש ב-configValue בתור LogName, צור PriceLogger
		// TODO: רשום את המשקיף למניה
	}

	// קרא את המחיר החדש
	newPriceStr, _ := reader.ReadString('\n')
	newPrice, _ := strconv.ParseFloat(strings.TrimSpace(newPriceStr), 64)

	// TODO: הגדר את המחיר החדש וקבל את התגובות

	// TODO: הדפס כל תגובה בשורה נפרדת
}
quiz iconבחנו את עצמכם

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

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

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