Menu
Coddy logo textTech

Dependency Injection

Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy. Lektion 45 von 107.

Dependency Injection ist eine Technik, bei der eine Struktur ihre Abhängigkeiten von außen erhält, anstatt sie intern zu erstellen. In Go machen Interfaces dieses Muster natürlich und leistungsfähig.

Anstatt eine bestimmte Implementierung innerhalb einer Struktur fest zu codieren, akzeptierst du ein Interface. Dadurch kannst du Implementierungen austauschen, ohne den Code der Struktur zu ändern:

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
}

Erstelle nun eine Struktur, die von der Schnittstelle und nicht von einem konkreten Typ abhängt:

type OrderService struct {
    notifier Notifier  // Abhängigkeit über Interface injiziert
}

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

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

Der OrderService weiß nicht und kümmert sich nicht darum, ob er E-Mail oder SMS verwendet. Du injizierst die Abhängigkeit beim Erstellen des Dienstes:

func main() {
    emailService := NewOrderService(EmailNotifier{})
    fmt.Println(emailService.PlaceOrder("Book"))  // Email: Bestellung aufgegeben: Book
    
    smsService := NewOrderService(SMSNotifier{})
    fmt.Println(smsService.PlaceOrder("Phone"))   // SMS: Bestellung aufgegeben: Phone
}

Dieser Ansatz macht deinen Code flexibler und besser testbar. Während des Testens kannst du einen Mock-Notifier einschleusen, der tatsächlich keine Nachrichten sendet. In der Produktion schleust du die echte Implementierung ein. Die Struktur bleibt in beiden Szenarien unverändert.

challenge icon

Aufgabe

Einfach

Erstellen wir ein Protokollierungssystem, das die Leistungsfähigkeit der Abhängigkeitsinjektion demonstriert. Du erstellst einen Dienst, der Protokolle an verschiedene Ziele schreiben kann, ohne dass der Dienst weiß oder sich darum kümmern muss, wohin diese Protokolle tatsächlich gehen.

Du organisierst deinen Code über drei Dateien:

  • logger.go: Definiere ein Logger-Interface, das eine Log(message string) string-Methode erfordert. Erstelle dann zwei verschiedene Logger-Implementierungen:
    • ConsoleLogger mit einem Prefix-Feld. Seine Log-Methode gibt [CONSOLE] [Prefix]: [message] zurück
    • FileLogger mit einem Filename-Feld. Seine Log-Methode gibt [FILE:[Filename]] [message] zurück
  • service.go: Erstelle eine AppService-Struktur, die vom Logger-Interface abhängt (nicht von einem konkreten Typ). Füge eine Konstruktorfunktion NewAppService hinzu, die einen Logger akzeptiert und einen Zeiger auf AppService zurückgibt. Gib AppService eine Methode namens DoWork(task string) string, die den injizierten Logger verwendet, um die Nachricht Processing: [task] zu protokollieren, und alles zurückgibt, was der Logger zurückgibt.
  • main.go: Lies die Konfiguration aus der Eingabe, erstelle beide Logger-Typen, injiziere jeden von ihnen in eine separate AppService-Instanz und rufe bei jedem Dienst DoWork mit der bereitgestellten Aufgabe auf. Gib das Ergebnis jedes Dienstaufrufs aus.

Die folgenden Eingaben werden bereitgestellt:

  • Zeile 1: Präfix des Konsolenloggers
  • Zeile 2: Dateiname des Dateiloggers
  • Zeile 3: Zu verarbeitende Aufgabe

Wenn beispielsweise INFO, app.log und user authentication angegeben werden, sollte deine Ausgabe folgendermaßen aussehen:

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

Beachte, dass AppService nicht weiß, ob in eine Konsole oder in eine Datei protokolliert wird. Es ruft einfach Log für den injizierten Logger auf. Diese Flexibilität ist das Wesentliche der Abhängigkeitsinjektion: Derselbe Dienstcode funktioniert mit völlig unterschiedlichen Protokollierungsimplementierungen.

Probier es selbst

package main

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

func main() {
	reader := bufio.NewReader(os.Stdin)
	
	// Konsolen-Logger-Präfix lesen
	prefix, _ := reader.ReadString('\n')
	prefix = prefix[:len(prefix)-1]
	
	// Datei-Logger-Dateinamen lesen
	filename, _ := reader.ReadString('\n')
	filename = filename[:len(filename)-1]
	
	// Zu verarbeitende Aufgabe lesen
	task, _ := reader.ReadString('\n')
	if len(task) > 0 && task[len(task)-1] == '\n' {
		task = task[:len(task)-1]
	}
	
	// TODO: Erstelle einen ConsoleLogger mit dem Präfix
	
	// TODO: Erstelle einen FileLogger mit dem Dateinamen
	
	// TODO: Erstelle einen AppService mit dem injizierten ConsoleLogger
	
	// TODO: Erstelle einen weiteren AppService mit dem injizierten FileLogger
	
	// TODO: Rufe DoWork auf jedem Service mit der Aufgabe auf und gib die Ergebnisse aus
	fmt.Println("result1")
	fmt.Println("result2")
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung

Übe selbstständig: Online-Go-Compiler