L’interfaccia come contratto
Fa parte della sezione Programmazione orientata agli oggetti del percorso GO di Coddy. Lezione 27 di 107.
Pensare alle interfacce come a contratti ti aiuta a progettare programmi Go migliori. Quando una funzione accetta un tipo interfaccia, sta dicendo: "Non mi interessa quale tipo concreto mi dai, purché rispetti questo contratto."
Questo modo di pensare basato sui contratti ti consente di scrivere funzioni che si concentrano sul comportamento anziché su tipi specifici. Considera una funzione che deve elaborare dati provenienti da varie origini:
type DataSource interface {
FetchData() string
}
func ProcessData(src DataSource) {
data := src.FetchData()
fmt.Println("Processing:", data)
}La funzione ProcessData non sa né le importa se riceve una connessione al database, un client API o un lettore di file. Richiede solo che l'input possa eseguire FetchData(). Qualsiasi tipo che rispetti questo contratto va bene.
type Database struct{ ConnectionString string }
func (d Database) FetchData() string { return "data from DB" }
type APIClient struct{ Endpoint string }
func (a APIClient) FetchData() string { return "data from API" }
func main() {
db := Database{ConnectionString: "localhost"}
api := APIClient{Endpoint: "https://api.example.com"}
ProcessData(db) // Funziona!
ProcessData(api) // Funziona anche questo!
}Anche questo approccio basato sui contratti rende il tuo codice più facile da testare. Durante i test, puoi creare un tipo fittizio che soddisfi l'interfaccia senza bisogno di database reali o connessioni di rete. La funzione sotto test non si accorge della differenza, perché il contratto è rispettato.
Sfida
FacileCostruiamo un sistema di notifiche che mostri come le interfacce fungano da contratti. Creerai un’interfaccia Notifier che definisce cosa significa inviare una notifica, poi implementerai più canali di notifica che rispettino tutti questo contratto.
Organizzerai il codice in due file:
notifiers.go: Definisci un’interfacciaNotifiercon un solo metodoNotify(message string) string. Poi crea tre struct diversi che soddisfino questo contratto:ConsoleNotifiercon un campoPrefixFileNotifiercon un campoFilenameWebhookNotifiercon un campoURL
Notifyper restituire una stringa che descriva come verrebbe inviata la notifica.main.go: Crea una funzione chiamataBroadcastAlertche accetti uno slice di valoriNotifiere una stringa contenente il messaggio. Questa funzione dovrebbe chiamareNotifysu ogni notifier e stampare i risultati. Leggi la configurazione dall’input, crea tutti e tre i tipi di notifier e invia un avviso tramite ciascuno di essi.
Verranno forniti i seguenti input:
- Riga 1: Prefisso della console
- Riga 2: Nome del file
- Riga 3: URL del webhook
- Riga 4: Messaggio di avviso
I tuoi metodi Notify dovrebbero restituire stringhe con i seguenti formati:
- ConsoleNotifier:
[Prefix] [message] - FileNotifier:
Writing to [Filename]: [message] - WebhookNotifier:
POST [URL]: [message]
Per esempio, con ALERT:, log.txt, https://hooks.example.com e Server down!, l’output dovrebbe essere:
ALERT: Server down!
Writing to log.txt: Server down!
POST https://hooks.example.com: Server down!Il vantaggio di questo design è che la tua funzione BroadcastAlert non conosce né si preoccupa dei tipi specifici di notifier. Richiede solo che ognuno rispetti il contratto Notifier. In seguito potresti aggiungere uno SlackNotifier o un EmailNotifier senza modificare affatto la funzione di invio.
Provalo tu
package main
import (
"bufio"
"fmt"
"os"
)
// BroadcastAlert invia un messaggio tramite tutti i notificatori
// TODO: Implementa questa funzione in modo da:
// - Accettare una slice di valori Notifier e una stringa di messaggio
// - Chiamare Notify su ogni notificatore e stampare i risultati
func BroadcastAlert(notifiers []Notifier, message string) {
// TODO: Scorri notifiers e stampa il risultato di ogni notifica
}
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Leggi il prefisso della console
scanner.Scan()
prefix := scanner.Text()
// Leggi il nome del file
scanner.Scan()
filename := scanner.Text()
// Leggi l'URL del webhook
scanner.Scan()
url := scanner.Text()
// Leggi il messaggio di avviso
scanner.Scan()
message := scanner.Text()
// TODO: Crea istanze di tutti e tre i tipi di notificatore
// - ConsoleNotifier con il prefisso
// - FileNotifier con filename
// - WebhookNotifier con l'URL
// TODO: Crea una slice di Notifier contenente tutti e tre i notifier
// TODO: Chiama BroadcastAlert con i notifier e il messaggio
_ = prefix
_ = filename
_ = url
_ = message
fmt.Println("TODO: Implement the solution")
}
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
1Fondamenti di OOP in Go
File esterniWorkspace e moduli GoPacchetti e importazioniNomi esportati e non esportatiIntroduzione all’OOP in GoGli struct come classiDefinire metodi sugli structReceiver con puntatore vs receiver con valoreInizializzazione degli structFunzioni costruttriciRiepilogo - Calcolatrice semplice4Interfacce
Introduzione alle interfacceImplementazione implicitaL’interfaccia come contrattoInterfaccia vuota (any)Asserzione di tipoSwitch sul tipoComposizione di interfacceInterfacce Stringer ed ErrorRipasso - Calcolatore di forme7Incapsulamento
Campi esportati e non esportatiIncapsulamento a livello di packageMetodi Getter e SetterNascondimento delle informazioni in GoRiepilogo - Registri degli studenti10Tipi generici (Go 1.18+)
Introduzione ai tipi genericiParametri di tipoVincoli di tipoStruct genericheSoluzione alternativa per i metodi genericiRiepilogo - Collezione generica13Modelli di progettazione Parte 1
Introduzione ai modelli di progettazioneModello SingletonModello FactoryModello Abstract FactoryModello ObserverModello Strategy2Approfondimento su tipi e struct
Tipi di base e compostiDefinizioni di tipi personalizzatiTag delle structStruct anonimeStruct annidateValori zero e predefinitiRipasso - Rubrica5Composizione invece dell'ereditarietà
Perché Go non ha l'ereditarietàFondamenti dell'embedding delle structPromozione dei metodiEmbedding di più structEmbedding e aggregazione a confrontoMascheramento dei metodi embeddedRiepilogo: gerarchia dei dipendenti8Gestione degli errori e OOP
L'interfaccia errorTipi di errore personalizzatiWrapping degli errori (fmt.Errorf)Errori sentinellaerrors.Is() ed errors.As()Panic, Defer e RecoverRiepilogo - Parser di file11Libreria standard e OOP
io.Reader e io.Writersort.InterfaceInterfaccia fmt.Stringerencoding/json con le structInterfaccia http.HandlerRipasso - modelli REST API14Modelli di progettazione – Parte 2
Pattern CommandPattern AdapterPattern DecoratorPattern Template MethodPattern StatePattern CompositeMiddleware come Decorator3Puntatori e memoria
Concetti base dei puntatori in GoPuntatori a structPassaggio per valore vs per riferimentoLa funzione new()Garbage collection in GoRiepilogo - Costruttore di liste concatenate6Polimorfismo in Go
Polimorfismo tramite interfacceDuck typing in GoRegole di soddisfacimento delle interfacceCollezioni polimorficheIniezione delle dipendenzeRiepilogo - Elaboratore di pagamenti9Concorrenza e OOP
Basi delle goroutineCanali e comunicazioneCanali bufferizzati vs non bufferizzatiIstruzione selectsync.Mutex e sync.RWMutexsync.WaitGroupProgettazione di struct thread-safeRiepilogo: worker poolEsercitati da solo: Compilatore Go online