Lezioni del modulo (5/5)
Pattern del costruttore `New...`
Go non ha constructor come parola chiave. La convenzione è una
funzione factory che ritorna un valore o un puntatore correttamente
inizializzato. Per nome si chiama tipicamente NewT (o Newxxx).
Pattern base: NewT
type User struct {
Name string
Age int
}
func NewUser(name string, age int) *User {
return &User{Name: name, Age: age}
}
u := NewUser("Ada", 36)
fmt.Println(u.Name)Vantaggi rispetto al letterale diretto &User{...}:
- Centralizzi la validazione e i default.
- Puoi avere campi non esportati e popolarli solo dal costruttore.
- Cambi l'implementazione senza rompere i chiamanti.
Costruttore con validazione: (*T, error)
Quando l'inizializzazione può fallire, ritorna (*T, error):
import "errors"
func NewUser(name string, age int) (*User, error) {
if name == "" {
return nil, errors.New("nome obbligatorio")
}
if age < 0 {
return nil, errors.New("eta non valida")
}
return &User{Name: name, Age: age}, nil
}
u, err := NewUser("Ada", 36)
if err != nil {
// gestisci...
}Questo è il pattern idiomatico per qualunque costruttore con precondizioni: db connection, file handle, server config, ecc.
Costruttori multipli
Se hai più "modi" di costruire, usa nomi descrittivi:
func NewUser(name string) *User { ... }
func NewUserFromJSON(data []byte) (*User, error) { ... }
func NewAdmin() *User { ... }Niente overload in Go: il nome distingue.
Functional options (cenno avanzato)
Quando i parametri sono molti e opzionali, il pattern idiomatico è "functional options":
type Option func(*Server)
func WithPort(p int) Option {
return func(s *Server) { s.Port = p }
}
func WithTLS(cert, key string) Option {
return func(s *Server) { s.Cert, s.Key = cert, key }
}
func NewServer(opts ...Option) *Server {
s := &Server{Port: 8080} // default
for _, opt := range opts {
opt(s)
}
return s
}
s := NewServer(WithPort(9000), WithTLS("c.pem", "k.pem"))È più verboso di un letterale ma scala bene con molti parametri
opzionali e mantiene retro-compatibilità (aggiungere una nuova Option
non rompe i chiamanti esistenti).
Quando NON serve un costruttore
Se lo zero value del tipo è già utile, lascia che il chiamante usi il
letterale diretto. Esempio canonico: sync.Mutex non ha costruttore,
var m sync.Mutex è già un mutex pronto. Stesso per bytes.Buffer.
Vale la regola: "Make the zero value useful". Se ci riesci, niente costruttore obbligatorio.
Prova tu
Scrivi la funzione factory NewUser(name string, age int) *User che ritorna &User{Name: name, Age: age}.
Mostra suggerimento
Usa il letterale `&User{...}` per ritornare un puntatore.
Soluzione disponibile dopo 3 tentativi
Trasforma NewUser in (name string) (*User, error): se name == '' ritorna nil, errors.New('nome obbligatorio').
Mostra suggerimento
Se ci sono precondizioni, restituisci (*T, error) e ritorna nil + error in caso di fallimento.
Soluzione disponibile dopo 3 tentativi
Qual è la convenzione idiomatica per i costruttori in Go?
// Quale firma è più idiomatica?Recap
- Niente
constructor: si usa una funzioneNewT(oNewdentro al package omonimo). - Ritorna
*Tper il caso semplice;(*T, error)con precondizioni o validazione. - In errore: ritorna
nilcome puntatore, l'errore esplicativo come secondo valore. - Niente overload; usa nomi descrittivi (
NewUserFromJSON,NewAdmin). - "Make the zero value useful": se ci riesci, il costruttore non serve.
- Pattern avanzato per molti parametri opzionali: functional options.