Duck typing in Go
Fa parte della sezione Programmazione orientata agli oggetti del percorso GO di Coddy. Lezione 42 di 107.
Il termine duck typing deriva dal detto: "Se cammina come un'anatra e fa qua qua come un'anatra, allora è un'anatra." In Go, questo significa che un tipo non deve dichiarare esplicitamente di implementare un'interfaccia. Deve solo avere i metodi giusti.
Considera questa interfaccia e due tipi completamente non correlati:
type Quacker interface {
Quack() string
}
type Duck struct{}
func (d Duck) Quack() string { return "Quack!" }
type Robot struct{}
func (r Robot) Quack() string { return "Beep-quack!" }
Né Duck né Robot menzionano Quacker in alcun punto delle loro definizioni. Eppure entrambi soddisfano l’interfaccia semplicemente perché hanno un metodo Quack() con la firma corretta:
func MakeItQuack(q Quacker) {
fmt.Println(q.Quack())
}
func main() {
MakeItQuack(Duck{}) // Quack!
MakeItQuack(Robot{}) // Beep-quack!
}
Questa soddisfazione implicita è potente perché ti permette di definire interfacce dopo che i tipi esistono già. Puoi creare un’interfaccia che corrisponda ai tipi di pacchetti esterni senza modificarne il codice sorgente. I tipi non devono conoscere la tua interfaccia. Devono solo comportarsi correttamente.
Il duck typing in Go ti offre la flessibilità dei linguaggi dinamici mantenendo la sicurezza dei tipi in fase di compilazione. Il compilatore verifica che i tipi dispongano effettivamente dei metodi richiesti prima di consentirne l'uso come valori di interfaccia.
Sfida
FacileCostruiamo un sistema di messaggistica che dimostri il duck typing in azione. Creerai tipi completamente indipendenti che possono tutti inviare messaggi tramite un’interfaccia comune, senza che nessuno di essi dichiari esplicitamente di implementarla.
Organizzerai il codice in tre file:
messengers.go: Crea tre struct indipendenti che condividono lo stesso comportamento:Phonecon un campoNumber. Il suo metodoSendMessage(text string) stringrestituisceSMS to [Number]: [text]Computercon un campoEmail. Il suo metodoSendMessage(text string) stringrestituisceEmail to [Email]: [text]Pigeoncon un campoName. Il suo metodoSendMessage(text string) stringrestituisce[Name] carries: [text]
sender.go: Definisci un’interfacciaMessengerche richiede un metodoSendMessage(text string) string. Crea una funzione chiamataBroadcastche accetta una slice diMessengere una stringa contenente un messaggio, quindi restituisce una slice di stringhe con il risultato della chiamata aSendMessagesu ciascun messaggero.main.go: Leggi i dati dall’input, crea un elemento per ciascun tipo di messaggero, raccoglili in una slice diMessengere usaBroadcastper inviare un messaggio tramite tutti. Stampa ogni risultato su una riga separata.
Verranno forniti i seguenti input:
- Riga 1: Numero di telefono
- Riga 2: Indirizzo email
- Riga 3: Nome del piccione
- Riga 4: Messaggio da inviare a tutti
Ad esempio, dati 555-1234, alice@mail.com, Winston e Hello World, l’output dovrebbe essere:
SMS to 555-1234: Hello World
Email to alice@mail.com: Hello World
Winston carries: Hello WorldL’intuizione fondamentale è che Phone, Computer e Pigeon non hanno nulla in comune. Non condividono un tipo base e non implementano esplicitamente Messenger. Eppure, poiché tutti «starnazzano» allo stesso modo (hanno il metodo corrispondente), Go consente di usarli tutti come valori Messenger. Questo è il duck typing in azione.
Provalo tu
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Leggi il numero di telefono
scanner.Scan()
phoneNumber := scanner.Text()
// Leggi l'indirizzo email
scanner.Scan()
email := scanner.Text()
// Leggi il nome del piccione
scanner.Scan()
pigeonName := scanner.Text()
// Leggi il messaggio da trasmettere
scanner.Scan()
message := scanner.Text()
// TODO: Crea un'istanza per ogni tipo di messaggero (Phone, Computer, Pigeon)
// TODO: Raccogli tutti i messaggeri in uno slice di Messenger
// TODO: Usa Broadcast per inviare il messaggio tramite tutti i messaggeri
// TODO: Stampa ogni risultato su una riga separata
// Segnaposto per utilizzare le variabili (rimuovere durante l'implementazione)
_ = phoneNumber
_ = email
_ = pigeonName
_ = message
}
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