00
:
00
:
00
:
00
•Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

Metodi in Go

I metodi in Go sono funzioni associate a un tipo specifico tramite un parametro speciale chiamato receiver. A differenza dei linguaggi orientati agli oggetti tradizionali, in Go i metodi non vengono dichiarati all’interno di una classe, ma sono funzioni con un receiver che indica su quale tipo operano. In questa guida esploreremo i metodi, i tipi di receiver e le best practice.

Definizione di un Metodo

Un metodo e una funzione con un parametro receiver tra la parola chiave func e il nome del metodo.

package main

import "fmt"

type Rettangolo struct {
    Larghezza float64
    Altezza   float64
}

// Metodo con value receiver
func (r Rettangolo) Area() float64 {
    return r.Larghezza * r.Altezza
}

func (r Rettangolo) Perimetro() float64 {
    return 2 * (r.Larghezza + r.Altezza)
}

func main() {
    ret := Rettangolo{Larghezza: 5, Altezza: 3}
    fmt.Printf("Area: %.1f\n", ret.Area())         // Area: 15.0
    fmt.Printf("Perimetro: %.1f\n", ret.Perimetro()) // Perimetro: 16.0
}

Il receiver (r Rettangolo) indica che il metodo appartiene al tipo Rettangolo. La variabile r e la copia dell’istanza su cui viene chiamato il metodo.

Value Receiver vs Pointer Receiver

La scelta tra value receiver e pointer receiver e fondamentale in Go.

Value Receiver

Il value receiver lavora su una copia del valore. Le modifiche non influenzano l’originale.

type Contatore struct {
    Valore int
}

// Value receiver: lavora su una copia
func (c Contatore) IncrementaSbagliato() {
    c.Valore++ // Modifica la copia, non l'originale
}

Pointer Receiver

Il pointer receiver lavora sul valore originale, permettendo le modifiche.

// Pointer receiver: modifica l'originale
func (c *Contatore) Incrementa() {
    c.Valore++
}

func (c *Contatore) Reset() {
    c.Valore = 0
}

func main() {
    c := Contatore{Valore: 0}
    c.Incrementa()
    c.Incrementa()
    c.Incrementa()
    fmt.Println(c.Valore) // 3

    c.Reset()
    fmt.Println(c.Valore) // 0
}

Go converte automaticamente tra valore e puntatore quando si chiama un metodo: c.Incrementa() viene automaticamente interpretato come (&c).Incrementa().

Quando Usare Pointer Receiver

Usa un pointer receiver quando:

  1. Il metodo deve modificare il receiver
  2. La struct e grande e vuoi evitare la copia
  3. Per coerenza: se un metodo del tipo usa pointer receiver, tutti gli altri dovrebbero farlo
type Utente struct {
    Nome   string
    Email  string
    Attivo bool
}

// Tutti i metodi usano pointer receiver per coerenza
func (u *Utente) Disattiva() {
    u.Attivo = false
}

func (u *Utente) CambiaEmail(nuovaEmail string) {
    u.Email = nuovaEmail
}

func (u *Utente) String() string {
    stato := "attivo"
    if !u.Attivo {
        stato = "inattivo"
    }
    return fmt.Sprintf("%s (%s) - %s", u.Nome, u.Email, stato)
}

Metodi su Qualsiasi Tipo

I metodi possono essere definiti su qualsiasi tipo definito nel pacchetto corrente, non solo sulle struct.

package main

import "fmt"

type Celsius float64
type Fahrenheit float64

func (c Celsius) InFahrenheit() Fahrenheit {
    return Fahrenheit(c*9/5 + 32)
}

func (f Fahrenheit) InCelsius() Celsius {
    return Celsius((f - 32) * 5 / 9)
}

func main() {
    temp := Celsius(100)
    fmt.Printf("%.1f C = %.1f F\n", temp, temp.InFahrenheit())
    // 100.0 C = 212.0 F

    tempF := Fahrenheit(72)
    fmt.Printf("%.1f F = %.1f C\n", tempF, tempF.InCelsius())
    // 72.0 F = 22.2 C
}

Non si possono definire metodi su tipi provenienti da altri pacchetti. Per farlo, bisogna creare un tipo locale basato su quello esterno.

Method Set (Insieme di Metodi)

Il method set di un tipo determina quali interfacce puo soddisfare:

  • Il method set di un valore T include solo i metodi con value receiver (t T)
  • Il method set di un puntatore *T include sia i metodi con value receiver che quelli con pointer receiver
type Forma interface {
    Area() float64
}

type Cerchio struct {
    Raggio float64
}

func (c Cerchio) Area() float64 {
    return 3.14159 * c.Raggio * c.Raggio
}

func main() {
    c := Cerchio{Raggio: 5}

    var f Forma
    f = c   // OK: Cerchio ha il metodo Area() con value receiver
    f = &c  // OK: *Cerchio include i metodi di Cerchio
    fmt.Println(f.Area())
}

Se Area() avesse un pointer receiver, solo &c (non c) potrebbe essere assegnato a f.

Espressioni di Metodo (Method Expressions)

Un metodo puo essere usato come valore di funzione tramite le method expressions.

type Calcolatrice struct{}

func (c Calcolatrice) Somma(a, b int) int    { return a + b }
func (c Calcolatrice) Prodotto(a, b int) int  { return a * b }

func main() {
    calc := Calcolatrice{}

    // Method expression: il receiver diventa il primo parametro
    somma := Calcolatrice.Somma
    risultato := somma(calc, 5, 3)
    fmt.Println(risultato) // 8

    // Method value: il receiver e gia legato
    prodotto := calc.Prodotto
    fmt.Println(prodotto(4, 7)) // 28
}

Le method expressions sono utili quando si devono passare metodi come argomenti a funzioni di ordine superiore.

Metodi con Embedding

Quando una struct incorpora un’altra struct, i metodi della struct incorporata vengono “promossi” e sono accessibili direttamente.

type Logger struct{}

func (l Logger) Log(messaggio string) {
    fmt.Println("[LOG]", messaggio)
}

type Server struct {
    Logger // Embedding
    Porta  int
}

func main() {
    s := Server{Porta: 8080}
    s.Log("Server avviato") // Chiama Logger.Log direttamente
}

Pattern Builder con Metodi

Un pattern comune e il builder con metodi che restituiscono il puntatore al receiver per permettere il concatenamento.

type QueryBuilder struct {
    tabella    string
    condizioni []string
    limite     int
}

func (q *QueryBuilder) Da(tabella string) *QueryBuilder {
    q.tabella = tabella
    return q
}

func (q *QueryBuilder) Dove(condizione string) *QueryBuilder {
    q.condizioni = append(q.condizioni, condizione)
    return q
}

func (q *QueryBuilder) Limite(n int) *QueryBuilder {
    q.limite = n
    return q
}

func main() {
    query := (&QueryBuilder{}).
        Da("utenti").
        Dove("eta > 18").
        Dove("attivo = true").
        Limite(10)

    fmt.Printf("Tabella: %s, Condizioni: %v, Limite: %d\n",
        query.tabella, query.condizioni, query.limite)
}

Conclusione

I metodi in Go offrono un modo elegante per associare comportamento ai tipi. La scelta tra value receiver e pointer receiver e una decisione di design importante: usa pointer receiver quando il metodo modifica il receiver o la struct e grande. Il method set determina quali interfacce un tipo soddisfa. L’embedding promuove automaticamente i metodi, offrendo composizione anziche ereditarieta. Comprendere i metodi e propedeutico per padroneggiare le interfacce in Go.