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
nilnu este ea însășinil:var p *Cerc; var f Forma = pdă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.