Vai al contenuto
eLearner.app
Modulo 5 · Lezione 5 di 525/50 nel corso~10 min
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

Go
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):

Go
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:

Go
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":

Go
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

Esercizio#go.m5.l5.e1
Tentativi: 0Caricamento…

Scrivi la funzione factory NewUser(name string, age int) *User che ritorna &User{Name: name, Age: age}.

Caricamento editor…
Mostra suggerimento

Usa il letterale `&User{...}` per ritornare un puntatore.

Soluzione disponibile dopo 3 tentativi

Esercizio#go.m5.l5.e2
Tentativi: 0Caricamento…

Trasforma NewUser in (name string) (*User, error): se name == '' ritorna nil, errors.New('nome obbligatorio').

Caricamento editor…
Mostra suggerimento

Se ci sono precondizioni, restituisci (*T, error) e ritorna nil + error in caso di fallimento.

Soluzione disponibile dopo 3 tentativi

Quiz#go.m5.l5.e3
Pronto

Qual è la convenzione idiomatica per i costruttori in Go?

Go
// Quale firma è più idiomatica?
Opzioni di risposta

Recap

  • Niente constructor: si usa una funzione NewT (o New dentro al package omonimo).
  • Ritorna *T per il caso semplice; (*T, error) con precondizioni o validazione.
  • In errore: ritorna nil come 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.