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.
Aufgabe
EinfachLassen 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 einLoggerInterface, das eineLog(message string) stringMethode erfordert. Erstellen Sie dann zwei verschiedene Logger-Implementierungen:ConsoleLoggermit einemPrefixFeld – seineLogMethode gibt[CONSOLE] [Prefix]: [message]zurückFileLoggermit einemFilenameFeld – seineLogMethode gibt[FILE:[Filename]] [message]zurück
service.go: Erstellen Sie einAppServiceStruct, das vomLoggerInterface abhängt (nicht von einem konkreten Typ). Fügen Sie eine Konstruktor-FunktionNewAppServicehinzu, die einenLoggerakzeptiert und einen Pointer aufAppServicezurückgibt. Geben SieAppServiceeine Methode namensDoWork(task string) string, die den injizierten Logger verwendet, um die NachrichtProcessing: [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 separateAppServiceInstanzen und rufen SieDoWorkfü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 authenticationBeachten 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")
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der Go OOP
Externe DateienGo Workspace & ModulePackages & ImportsExportierte vs. nicht-exportierte NamenEinführung in OOP in GoStructs als KlassenMethoden für Structs definierenPointer- vs. Value-ReceiverStruct-InitialisierungKonstruktor-FunktionenZusammenfassung – Einfacher Taschenrechner4Interfaces
Einführung in InterfacesImplizite ImplementierungInterface als VertragLeeres Interface (any)Type AssertionType SwitchInterface-KompositionStringer & Error InterfacesRückblick - Formen-Rechner7Kapselung
Exportierte vs. nicht exportierte FelderKapselung auf Paket-EbeneGetter- & Setter-MethodenInformation Hiding in GoZusammenfassung - Studierendendatensätze10Generics (Go 1.18+)
Einführung in GenericsTypparameterType ConstraintsGenerische StructsWorkaround für generische MethodenZusammenfassung – Generische Collection2Vertiefung: Typen & Structs
Basis- & zusammengesetzte TypenBenutzerdefinierte TypdefinitionenStruct TagsAnonyme StructsVerschachtelte StructsZero Values & StandardwerteZusammenfassung - Kontaktbuch5Komposition statt Vererbung
Warum Go keine Vererbung hatGrundlagen des Struct EmbeddingMethod PromotionEinbetten mehrerer StructsEmbedding vs. AggregationShadowing eingebetteter MethodenZusammenfassung – Mitarbeiter-Hierarchie8Fehlerbehandlung & OOP
Das error-InterfaceEigene FehlertypenError Wrapping (fmt.Errorf)Sentinel-Fehlererrors.Is() und errors.As()Panic, Defer und RecoverZusammenfassung – Datei-Parser3Zeiger & Speicher
Zeiger-Grundlagen in GoZeiger auf StructsWertübergabe vs. ReferenzübergabeDie new() FunktionGarbage Collection in GoWiederholung – Linked List Builder6Polymorphismus in Go
Polymorphismus über InterfacesDuck Typing in GoInterface-ErfüllungsregelnPolymorphe CollectionsDependency InjectionZusammenfassung – Payment Processor9Nebenläufigkeit & OOP
Goroutines GrundlagenChannels & KommunikationGepufferte vs. ungepufferte ChannelsSelect-Anweisungsync.Mutex & sync.RWMutexsync.WaitGroupThread-sicheres Struct-DesignZusammenfassung - Worker Pool