Wstrzykiwanie zależności
Część sekcji Programowanie obiektowe ścieżki GO w Coddy. Lekcja 45 z 107.
Wstrzykiwanie zależności to technika, w której struktura otrzymuje swoje zależności z zewnątrz, zamiast tworzyć je wewnętrznie. W Go interfejsy sprawiają, że ten wzorzec jest naturalny i potężny.
Zamiast na stałe kodować konkretną implementację w strukturze, akceptujesz interfejs. Dzięki temu możesz zmieniać implementacje bez modyfikowania kodu struktury:
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
}
Teraz utwórz strukturę, która zależy od interfejsu, a nie od konkretnego typu:
type OrderService struct {
notifier Notifier // zależność wstrzyknięta przez interfejs
}
func NewOrderService(n Notifier) *OrderService {
return &OrderService{notifier: n}
}
func (o *OrderService) PlaceOrder(item string) string {
return o.notifier.Send("Order placed: " + item)
}
OrderService nie wie ani nie obchodzi go, czy używa poczty e-mail, czy SMS-ów. Wstrzykujesz zależność podczas tworzenia usługi:
func main() {
emailService := NewOrderService(EmailNotifier{})
fmt.Println(emailService.PlaceOrder("Book")) // Email: Zamówienie złożone: Book
smsService := NewOrderService(SMSNotifier{})
fmt.Println(smsService.PlaceOrder("Phone")) // SMS: Zamówienie złożone: Phone
}
To podejście sprawia, że Twój kod jest bardziej elastyczny i łatwiejszy do testowania. Podczas testowania możesz wstrzyknąć atrapę powiadamiacza, która w rzeczywistości nie wysyła wiadomości. W środowisku produkcyjnym wstrzykujesz rzeczywistą implementację. Struktura pozostaje niezmieniona w obu przypadkach.
Wyzwanie
ŁatwyZbudujmy system logowania, który pokazuje, jaką moc ma wstrzykiwanie zależności. Utworzysz usługę, która może zapisywać logi w różnych miejscach docelowych, nie wiedząc ani nie przejmując się tym, dokąd faktycznie trafiają.
Rozmieścisz kod w trzech plikach:
logger.go: Zdefiniuj interfejsLogger, który wymaga metodyLog(message string) string. Następnie utwórz dwie różne implementacje loggera:ConsoleLoggerz polemPrefix. Jego metodaLogzwraca[CONSOLE] [Prefix]: [message]FileLoggerz polemFilename. Jego metodaLogzwraca[FILE:[Filename]] [message]
service.go: Utwórz strukturęAppService, która zależy od interfejsuLogger(a nie od konkretnego typu). Dodaj funkcję konstruktoraNewAppService, która przyjmujeLoggeri zwraca wskaźnik doAppService. Dodaj doAppServicemetodę o nazwieDoWork(task string) string, która używa wstrzykniętego loggera do zalogowania komunikatuProcessing: [task]i zwraca wartość zwróconą przez logger.main.go: Odczytaj konfigurację z wejścia, utwórz oba typy loggera, wstrzyknij każdy z nich do osobnej instancjiAppServicei wywołajDoWorkw każdej usłudze, przekazując podane zadanie. Wypisz wynik każdego wywołania usługi.
Podane zostaną następujące dane wejściowe:
- Wiersz 1: Prefiks loggera konsoli
- Wiersz 2: Nazwa pliku loggera plikowego
- Wiersz 3: Zadanie do przetworzenia
Na przykład dla danych INFO, app.log i user authentication wynik powinien być następujący:
[CONSOLE] INFO: Processing: user authentication
[FILE:app.log] Processing: user authenticationZauważ, że AppService nie wie, czy loguje do konsoli, czy do pliku. Po prostu wywołuje Log na dowolnym wstrzykniętym loggerze. Ta elastyczność jest istotą wstrzykiwania zależności: ten sam kod usługi działa z całkowicie różnymi implementacjami logowania.
Spróbuj swoich sił
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Odczytaj prefiks rejestratora konsolowego
prefix, _ := reader.ReadString('\n')
prefix = prefix[:len(prefix)-1]
// Odczytaj nazwę pliku rejestratora plikowego
filename, _ := reader.ReadString('\n')
filename = filename[:len(filename)-1]
// Odczytaj zadanie do przetworzenia
task, _ := reader.ReadString('\n')
if len(task) > 0 && task[len(task)-1] == '\n' {
task = task[:len(task)-1]
}
// TODO: Utwórz ConsoleLogger z prefiksem
// TODO: Utwórz FileLogger z nazwą pliku
// TODO: Utwórz AppService z wstrzykniętym ConsoleLogger
// TODO: Utwórz kolejny AppService z wstrzykniętym FileLogger
// TODO: Wywołaj DoWork w każdej usłudze, przekazując zadanie, i wypisz wyniki
fmt.Println("result1")
fmt.Println("result2")
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy OOP w Go
Pliki zewnętrzneObszar roboczy i moduły GoPakiety i importyNazwy eksportowane i nieeksportowaneWprowadzenie do OOP w GoStruktury jako klasyDefiniowanie metod dla strukturOdbiorniki wskaźnikowe i wartościoweInicjalizacja strukturFunkcje konstruktoraPodsumowanie – prosty kalkulator4Interfejsy
Wprowadzenie do interfejsówImplementacja niejawnaInterfejs jako kontraktPusty interfejs (any)Asercja typuPrzełącznik typówKompozycja interfejsówInterfejsy Stringer i ErrorPowtórka — kalkulator figur7Hermetyzacja
Pola eksportowane i nieeksportowaneHermetyzacja na poziomie pakietuMetody pobierające i ustawiająceUkrywanie informacji w GoPowtórka – dane studentów10Typy generyczne (Go 1.18+)
Wprowadzenie do typów generycznychParametry typówOgraniczenia typówStruktury generyczneObejście ograniczeń metod generycznychPodsumowanie — generyczna kolekcja2Typy i struktury w praktyce
Typy proste i złożoneDefiniowanie własnych typówTagi pól strukturStruktury anonimoweStruktury zagnieżdżoneWartości zerowe i domyślnePodsumowanie – książka kontaktów5Kompozycja zamiast dziedziczenia
Dlaczego Go nie ma dziedziczeniaPodstawy osadzania strukturPromowanie metodOsadzanie wielu strukturOsadzanie a agregacjaPrzesłanianie osadzonych metodPodsumowanie — hierarchia pracowników8Obsługa błędów i programowanie obiektowe
Interfejs błęduWłasne typy błędówOpakowywanie błędów (fmt.Errorf)Błędy wartowniczeerrors.Is() i errors.As()Panic, Defer i RecoverPodsumowanie — parser plików3Wskaźniki i pamięć
Podstawy wskaźników w GoWskaźniki do strukturPrzekazywanie przez wartość a przez referencjęFunkcja new()Odśmiecanie pamięci w GoPowtórka — kreator listy wiązanej6Polimorfizm w Go
Polimorfizm za pomocą interfejsówDuck typing w GoZasady spełniania interfejsówKolekcje polimorficzneWstrzykiwanie zależnościPowtórka — procesor płatności9Współbieżność i OOP
Podstawy goroutinesKanały i komunikacjaKanały buforowane i niebuforowaneInstrukcja selectsync.Mutex i sync.RWMutexsync.WaitGroupProjektowanie struktur bezpiecznych wątkowoPowtórka – pula workerów12Zaawansowane zagadnienia programowania obiektowego
Wzorzec opcji funkcyjnychWzorzec Builder w GoŁańcuchowe wywoływanie metodAliasy typów a definicje typówPodstawy refleksjiPrzegląd generowania koduPoćwicz samodzielnie: Kompilator Go online