Menu
Coddy logo textTech

הזרקת תלויות

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

הזרקת תלויות היא טכניקה שבה מבנה מקבל את התלויות שלו מבחוץ במקום ליצור אותן בעצמו. ב-Go, ממשקים הופכים את התבנית הזו לטבעית ועוצמתית.

במקום לקבע בקוד מימוש ספציפי בתוך struct, מקבלים ממשק. כך אפשר להחליף מימושים בלי לשנות את הקוד של ה־struct:

type Notifier interface {
    Send(message string) string
}

type EmailNotifier struct{}
func (e EmailNotifier) Send(message string) string {
    return "Email: " + message
}

type SMSNotifier struct{}
func (s SMSNotifier) Send(message string) string {
    return "SMS: " + message
}

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

type OrderService struct {
    notifier Notifier  // תלות המוזרקת באמצעות ממשק
}

func NewOrderService(n Notifier) *OrderService {
    return &OrderService{notifier: n}
}

func (o *OrderService) PlaceOrder(item string) string {
    return o.notifier.Send("Order placed: " + item)
}

ל־OrderService לא ידוע ולא אכפת אם הוא משתמש בדוא״ל או ב־SMS. מזריקים את התלות בעת יצירת השירות:

func main() {
    emailService := NewOrderService(EmailNotifier{})
    fmt.Println(emailService.PlaceOrder("Book"))  // Email: ההזמנה בוצעה: Book
    
    smsService := NewOrderService(SMSNotifier{})
    fmt.Println(smsService.PlaceOrder("Phone"))   // SMS: ההזמנה בוצעה: Phone
}

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

challenge icon

אתגר

קל

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

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

  • logger.go: הגדירו ממשק Logger שדורש מתודה Log(message string) string. לאחר מכן צרו שתי מימושים שונים של רושמי יומנים:
    • ConsoleLogger עם שדה Prefix. המתודה Log שלו מחזירה [CONSOLE] [Prefix]: [message]
    • FileLogger עם שדה Filename. המתודה Log שלו מחזירה [FILE:[Filename]] [message]
  • service.go: צרו מבנה AppService שתלוי בממשק Logger (ולא בטיפוס קונקרטי). כללו פונקציית בנאי NewAppService שמקבלת Logger ומחזירה מצביע ל-AppService. הוסיפו ל-AppService מתודה בשם DoWork(task string) string שמשתמשת ברושם היומנים שהוזרק כדי לרשום את ההודעה Processing: [task] ומחזירה את מה שרושם היומנים מחזיר.
  • main.go: קראו את התצורה מהקלט, צרו את שני סוגי רושמי היומנים, הזריקו כל אחד מהם למופע AppService נפרד, וקראו ל-DoWork בכל שירות עם המשימה שסופקה. הדפיסו את התוצאה של כל קריאה לשירות.

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

  • שורה 1: הקידומת של רושם היומנים למסוף
  • שורה 2: שם הקובץ של רושם היומנים לקובץ
  • שורה 3: המשימה לעיבוד

לדוגמה, עבור INFO, app.log ו-user authentication, הפלט שלכם אמור להיות:

[CONSOLE] INFO: Processing: user authentication
[FILE:app.log] Processing: user authentication

שימו לב ש-AppService לא יודע אם הוא רושם יומנים למסוף או לקובץ. הוא פשוט קורא ל-Log בכל רושם יומנים שהוזרק אליו. הגמישות הזאת היא המהות של הזרקת תלויות: אותו קוד שירות עובד עם מימושים שונים לחלוטין של רישום יומנים.

נסו בעצמכם

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	reader := bufio.NewReader(os.Stdin)
	
	// קרא את הקידומת של רושם הלוגים למסוף
	prefix, _ := reader.ReadString('\n')
	prefix = prefix[:len(prefix)-1]
	
	// קרא את שם הקובץ של רושם הלוגים לקובץ
	filename, _ := reader.ReadString('\n')
	filename = filename[:len(filename)-1]
	
	// קרא את המשימה לעיבוד
	task, _ := reader.ReadString('\n')
	if len(task) > 0 && task[len(task)-1] == '\n' {
		task = task[:len(task)-1]
	}
	
	// TODO: צור ConsoleLogger עם הקידומת
	
	// TODO: צור FileLogger עם שם הקובץ
	
	// TODO: צור AppService והזרק אליו את ConsoleLogger
	
	// TODO: צור AppService נוסף והזרק אליו את FileLogger
	
	// TODO: קרא ל-DoWork בכל אחד מהשירותים עם המשימה והדפס את התוצאות
	fmt.Println("result1")
	fmt.Println("result2")
}
quiz iconבחנו את עצמכם

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

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

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