Menu
Coddy logo textTech

Iniezione delle dipendenze

Fa parte della sezione Programmazione orientata agli oggetti del percorso GO di Coddy. Lezione 45 di 107.

L'iniezione delle dipendenze è una tecnica in cui una struct riceve le proprie dipendenze dall'esterno anziché crearle al suo interno. In Go, le interfacce rendono questo schema naturale ed efficace.

Invece di codificare rigidamente un’implementazione specifica all’interno di una struct, accetti un’interfaccia. Questo ti permette di sostituire le implementazioni senza modificare il codice della struct:

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
}

Ora crea una struct che dipende dall’interfaccia, non da un tipo concreto:

type OrderService struct {
    notifier Notifier  // dipendenza iniettata tramite interfaccia
}

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

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

OrderService non sa né gli interessa se sta usando email o SMS. Inietti la dipendenza quando crei il servizio:

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

Questo approccio rende il tuo codice più flessibile e testabile. Durante i test, puoi inserire un notificatore simulato che non invia davvero messaggi. In produzione, inserisci l’implementazione reale. La struct rimane invariata in entrambi gli scenari.

challenge icon

Sfida

Facile

Costruiamo un sistema di logging che dimostra la potenza dell'iniezione delle dipendenze. Creerai un servizio che può scrivere log in destinazioni diverse, senza che il servizio sappia o si preoccupi di dove finiscano effettivamente quei log.

Organizzerai il codice in tre file:

  • logger.go: Definisci un'interfaccia Logger che richiede un metodo Log(message string) string. Crea poi due implementazioni diverse di logger:
    • ConsoleLogger con un campo Prefix. Il suo metodo Log restituisce [CONSOLE] [Prefix]: [message]
    • FileLogger con un campo Filename. Il suo metodo Log restituisce [FILE:[Filename]] [message]
  • service.go: Crea una struct AppService che dipende dall'interfaccia Logger (non da un tipo concreto). Includi una funzione costruttrice NewAppService che accetta un Logger e restituisce un puntatore a AppService. Aggiungi a AppService un metodo chiamato DoWork(task string) string che usa il logger iniettato per registrare il messaggio Processing: [task] e restituisce ciò che restituisce il logger.
  • main.go: Leggi la configurazione dall'input, crea entrambi i tipi di logger, inietta ciascuno in un'istanza separata di AppService e chiama DoWork su ciascun servizio con l'attività fornita. Stampa il risultato di ogni chiamata al servizio.

Verranno forniti i seguenti input:

  • Riga 1: Prefisso del logger per la console
  • Riga 2: Nome file del logger su file
  • Riga 3: Attività da elaborare

Ad esempio, dati INFO, app.log e user authentication, l'output dovrebbe essere:

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

Nota che AppService non sa se sta registrando i log sulla console o su un file. Si limita a chiamare Log sul logger che gli è stato iniettato. Questa flessibilità è l'essenza dell'iniezione delle dipendenze: lo stesso codice del servizio funziona con implementazioni di logging completamente diverse.

Provalo tu

package main

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

func main() {
	reader := bufio.NewReader(os.Stdin)
	
	// Leggi il prefisso del logger della console
	prefix, _ := reader.ReadString('\n')
	prefix = prefix[:len(prefix)-1]
	
	// Leggi il nome del file del logger su file
	filename, _ := reader.ReadString('\n')
	filename = filename[:len(filename)-1]
	
	// Leggi l'attività da elaborare
	task, _ := reader.ReadString('\n')
	if len(task) > 0 && task[len(task)-1] == '\n' {
		task = task[:len(task)-1]
	}
	
	// TODO: Crea un ConsoleLogger con prefix
	
	// TODO: Crea un FileLogger con filename
	
	// TODO: Crea un AppService con ConsoleLogger iniettato
	
	// TODO: Crea un altro AppService con FileLogger iniettato
	
	// TODO: Chiama DoWork su ogni servizio con task e stampa i risultati
	fmt.Println("result1")
	fmt.Println("result2")
}
quiz iconMettiti alla prova

Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.

Tutte le lezioni di Programmazione orientata agli oggetti

Esercitati da solo: Compilatore Go online