Menu
Coddy logo textTech

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.

challenge icon

Wyzwanie

Łatwy

Zbudujmy 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 interfejs Logger, który wymaga metody Log(message string) string. Następnie utwórz dwie różne implementacje loggera:
    • ConsoleLogger z polem Prefix. Jego metoda Log zwraca [CONSOLE] [Prefix]: [message]
    • FileLogger z polem Filename. Jego metoda Log zwraca [FILE:[Filename]] [message]
  • service.go: Utwórz strukturę AppService, która zależy od interfejsu Logger (a nie od konkretnego typu). Dodaj funkcję konstruktora NewAppService, która przyjmuje Logger i zwraca wskaźnik do AppService. Dodaj do AppService metodę o nazwie DoWork(task string) string, która używa wstrzykniętego loggera do zalogowania komunikatu Processing: [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 instancji AppService i wywołaj DoWork w 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 authentication

Zauważ, ż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")
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator Go online