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 ein struct seine Abhängigkeiten von außen erhält, anstatt sie intern zu erstellen. In Go machen Interfaces dieses Muster natürlich und leistungsstark.

Anstatt eine spezifische Implementierung fest in einem Struct zu kodieren, akzeptieren Sie ein Interface. Dies ermöglicht es Ihnen, Implementierungen auszutauschen, ohne den Code des Structs 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
}

Erstellen Sie nun ein Struct, das von dem Interface abhängt und nicht von einem konkreten Typ:

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 es ist ihm egal, ob er E-Mail oder SMS verwendet. Sie injizieren 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 Ihren Code flexibler und testbarer. Während des Testens können Sie einen Mock-Notifier injizieren, der keine tatsächlichen Nachrichten sendet. In der Produktion injizieren Sie die echte Implementierung. Das struct bleibt in beiden Szenarien unverändert.

challenge icon

Aufgabe

Einfach

Lassen Sie uns ein Logging-System bauen, das die Leistungsfähigkeit von Dependency Injection demonstriert. Sie werden einen Dienst erstellen, der Protokolle an verschiedene Ziele schreiben kann – ohne dass der Dienst weiß oder sich darum kümmert, wohin diese Protokolle tatsächlich gehen.

Sie werden Ihren Code über drei Dateien organisieren:

  • logger.go: Definieren Sie ein Logger Interface, das eine Log(message string) string Methode erfordert. Erstellen Sie 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: Erstellen Sie ein AppService Struct, das vom Logger Interface abhängt (nicht von einem konkreten Typ). Fügen Sie eine Konstruktor-Funktion NewAppService hinzu, die einen Logger akzeptiert und einen Pointer auf AppService zurückgibt. Geben Sie AppService eine Methode namens DoWork(task string) string, die den injizierten Logger verwendet, um die Nachricht Processing: [task] zu protokollieren und das zurückgibt, was der Logger zurückgibt.
  • main.go: Lesen Sie die Konfiguration aus der Eingabe, erstellen Sie beide Logger-Typen, injizieren Sie jeden in separate AppService Instanzen und rufen Sie DoWork für jeden Dienst mit der angegebenen Aufgabe auf. Geben Sie das Ergebnis jedes Dienstaufrufs aus.

Die folgenden Eingaben werden bereitgestellt:

  • Zeile 1: Console logger prefix
  • Zeile 2: File logger filename
  • Zeile 3: Task to process

Zum Beispiel, gegeben INFO, app.log und user authentication, sollte Ihre Ausgabe wie folgt aussehen:

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

Beachten Sie, dass AppService nicht weiß, ob es in eine Konsole oder eine Datei protokolliert – es ruft einfach Log auf dem Logger auf, der injiziert wurde. Diese Flexibilität ist der Kern von Dependency Injection: Derselbe Dienst-Code funktioniert mit völlig unterschiedlichen Logging-Implementierungen.

Spickzettel

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

Definieren Sie ein Interface für die Abhängigkeit:

type Notifier interface {
    Send(message string) string
}

Erstellen Sie konkrete Implementierungen des Interfaces:

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
}

Erstellen Sie ein Struct, das vom Interface abhängt und nicht von einem konkreten Typ:

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)
}

Injizieren Sie die Abhängigkeit beim Erstellen des Services:

emailService := NewOrderService(EmailNotifier{})
fmt.Println(emailService.PlaceOrder("Book"))  // Email: Order placed: Book

smsService := NewOrderService(SMSNotifier{})
fmt.Println(smsService.PlaceOrder("Phone"))   // SMS: Order placed: Phone

Dieser Ansatz macht den Code flexibler und testbarer, da verschiedene Implementierungen ausgetauscht werden können, ohne den Code des Structs zu ändern.

Probier es selbst

package main

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

func main() {
	reader := bufio.NewReader(os.Stdin)
	
	// Präfix für den ConsoleLogger einlesen
	prefix, _ := reader.ReadString('\n')
	prefix = prefix[:len(prefix)-1]
	
	// Dateiname für den FileLogger einlesen
	filename, _ := reader.ReadString('\n')
	filename = filename[:len(filename)-1]
	
	// Zu verarbeitende Aufgabe einlesen
	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, in den der ConsoleLogger injiziert wird
	
	// TODO: Erstelle einen weiteren AppService, in den der FileLogger injiziert wird
	
	// TODO: Rufe DoWork für jeden 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