interface și interfețe în Go

Interfața descrie un set de metode; orice tip care le are o implementează automat, fără declarație explicită.

Sintaxă
type Forma interface {
    Aria() float64
    Perimetru() float64
}

var f Forma = Cerc{R: 2}

Ce face interface și interfețe

interface definește un comportament: o listă de metode. Un tip implementează interfața dacă are toate metodele ei, cu aceleași semnături. Nu există cuvântul implements: potrivirea se face automat. Așa, o funcție care primește un io.Writer poate scrie într-un fișier, într-un buffer sau într-un răspuns HTTP.

O variabilă de tip interfață ține o valoare concretă și tipul ei. Valoarea zero este nil. Pentru a recupera tipul concret se folosesc type assertion (f.(Cerc)) și type switch. Interfața goală interface{}, scrisă azi any, este satisfăcută de orice tip.

În Go se preferă interfețele mici, cu una sau două metode (fmt.Stringer, io.Reader, error), definite acolo unde sunt folosite, nu lângă tipul care le implementează. Interfețele pot conține alte interfețe (io.ReadWriter). În generics, interfețele servesc și drept constrângeri.

Exemple

Exemplul 1: Mai multe tipuri, aceeași interfață

package main

import (
    "fmt"
    "math"
)

type Forma interface {
    Aria() float64
    Nume() string
}

type Cerc struct{ R float64 }
type Dreptunghi struct{ L, l float64 }

func (c Cerc) Aria() float64       { return math.Pi * c.R * c.R }
func (c Cerc) Nume() string        { return "cerc" }
func (d Dreptunghi) Aria() float64 { return d.L * d.l }
func (d Dreptunghi) Nume() string  { return "dreptunghi" }

func main() {
    forme := []Forma{Cerc{R: 1}, Dreptunghi{L: 4, l: 2.5}, Cerc{R: 2}}
    total := 0.0
    for _, f := range forme {
        fmt.Printf("%-10s %.2f\n", f.Nume(), f.Aria())
        total += f.Aria()
    }
    fmt.Printf("aria totală: %.2f\n", total)
}

Rezultat

cerc       3.14
dreptunghi 10.00
cerc       12.57
aria totală: 25.71

Exemplul 2: io.Writer: aceeași funcție, destinații diferite

raport primește orice are metoda Write: ecranul (os.Stdout) sau un strings.Builder.

package main

import (
    "fmt"
    "io"
    "os"
    "strings"
)

func raport(w io.Writer, vanzari map[string]int) {
    fmt.Fprintf(w, "Iași: %d, Cluj: %d\n", vanzari["Iași"], vanzari["Cluj"])
}

func main() {
    v := map[string]int{"Iași": 120, "Cluj": 95}
    raport(os.Stdout, v)

    var b strings.Builder
    raport(&b, v)
    fmt.Printf("în memorie: %q\n", b.String())
}

Rezultat

Iași: 120, Cluj: 95
în memorie: "Iași: 120, Cluj: 95\n"

Exemplul 3: Verificare la compilare și interfața fmt.Stringer

Rândul var _ fmt.Stringer = Moneda{} nu face nimic la rulare, dar oprește compilarea dacă tipul nu are metoda.

package main

import "fmt"

type Moneda struct {
    Suma float64
    Cod  string
}

func (m Moneda) String() string { return fmt.Sprintf("%.2f %s", m.Suma, m.Cod) }

var _ fmt.Stringer = Moneda{} // verificare la compilare

func main() {
    var s fmt.Stringer = Moneda{149.9, "RON"}
    fmt.Println("preț:", s)
    var gol fmt.Stringer
    fmt.Println("interfață goală:", gol == nil)
}

Rezultat

preț: 149.90 RON
interfață goală: true

Greșeli frecvente

  • Metoda definită pe pointer (func (c *Cerc) Aria()) și valoarea pusă direct în interfață: Cerc does not implement Forma (method Aria has pointer receiver). Pune &Cerc{}.
  • Interfață care ține un pointer nil nu este ea însăși nil: var p *Cerc; var f Forma = p dă f != nil. Contează mai ales la erori.
  • Interfețe mari, definite „preventiv”: în Go se definesc când ai cel puțin două implementări sau ai nevoie de testare.

Comenzi înrudite

  • metode: Funcții legate de un tip prin receptor; cu receptor pointer pot modifica valoarea pe care sunt apelate.
  • type assertion: Scoate valoarea concretă dintr-o interfață: x.(T) verifică dacă interfața ține tipul T și îl returnează.
  • type switch: Un switch care alege ramura după tipul concret ținut de o interfață, cu variabila deja convertită.
  • any: Alias pentru interface{}: un tip care poate ține orice valoare; folosit și drept constrângere în generics.
  • generics (parametri de tip): Funcții și tipuri care lucrează cu mai multe tipuri, scrise o singură dată cu parametri de tip [T constrângere].
  • erori (error): În Go erorile sunt valori: funcțiile returnează un error, iar apelantul verifică if err != nil.