Go
interfacce
Ricerca…
Osservazioni
Le interfacce in Go sono solo set di metodi fissi. Un tipo implementa implicitamente un'interfaccia se il suo set di metodi è un superset dell'interfaccia. Non c'è una dichiarazione di intenti.
Interfaccia semplice
In Go, un'interfaccia è solo un insieme di metodi. Usiamo un'interfaccia per specificare un comportamento di un dato oggetto.
type Painter interface {
Paint()
}
Il tipo di implementazione non deve dichiarare che sta implementando l'interfaccia. È sufficiente definire i metodi della stessa firma.
type Rembrandt struct{}
func (r Rembrandt) Paint() {
// use a lot of canvas here
}
Ora possiamo usare la struttura come interfaccia.
var p Painter
p = Rembrandt{}
Un'interfaccia può essere soddisfatta (o implementata) da un numero arbitrario di tipi. Inoltre un tipo può implementare un numero arbitrario di interfacce.
type Singer interface {
Sing()
}
type Writer interface {
Write()
}
type Human struct{}
func (h *Human) Sing() {
fmt.Println("singing")
}
func (h *Human) Write() {
fmt.Println("writing")
}
type OnlySinger struct{}
func (o *OnlySinger) Sing() {
fmt.Println("singing")
}
Qui, la struttura Human
soddisfa sia l'interfaccia Singer
che quella Writer
, ma la struttura OnlySinger
soddisfa solo l'interfaccia Singer
.
Interfaccia vuota
C'è un tipo di interfaccia vuoto, che non contiene metodi. Lo dichiariamo come interface{}
. Questo non contiene metodi, quindi ogni type
soddisfa. Quindi l'interfaccia vuota può contenere qualsiasi valore di tipo.
var a interface{}
var i int = 5
s := "Hello world"
type StructType struct {
i, j int
k string
}
// all are valid statements
a = i
a = s
a = &StructType{1, 2, "hello"}
Il caso d'uso più comune per le interfacce è garantire che una variabile supporti uno o più comportamenti. Al contrario, il caso d'uso principale per l'interfaccia vuota è definire una variabile che può contenere qualsiasi valore, indipendentemente dal suo tipo concreto.
Per riportare questi valori come i loro tipi originali, dobbiamo solo farlo
i = a.(int)
s = a.(string)
m := a.(*StructType)
o
i, ok := a.(int)
s, ok := a.(string)
m, ok := a.(*StructType)
ok
indica se l' interface a
è convertibile in un determinato tipo. Se non è possibile il cast ok
sarà false
.
Valori dell'interfaccia
Se dichiari una variabile di un'interfaccia, può memorizzare qualsiasi tipo di valore che implementa i metodi dichiarati dall'interfaccia!
Se dichiariamo h
interface Singer
, può memorizzare un valore di tipo Human
o OnlySinger.
Ciò è dovuto al fatto che tutti implementano i metodi specificati dall'interfaccia Singer
.
var h Singer
h = &human{}
h.Sing()
Determinazione del tipo sottostante dall'interfaccia
In go a volte può essere utile sapere quale tipo di sottofondo ti è stato passato. Questo può essere fatto con un interruttore di tipo. Questo presuppone che abbiamo due strutture:
type Rembrandt struct{} func (r Rembrandt) Paint() {} type Picasso struct{} func (r Picasso) Paint() {}
Che implementano l'interfaccia di Painter:
type Painter interface { Paint() }
Quindi possiamo usare questa opzione per determinare il tipo sottostante:
func WhichPainter(painter Painter) { switch painter.(type) { case Rembrandt: fmt.Println("The underlying type is Rembrandt") case Picasso: fmt.Println("The underlying type is Picasso") default: fmt.Println("Unknown type") } }
Controllo in fase di compilazione se un tipo soddisfa un'interfaccia
Interfacce e implementazioni (tipi che implementano un'interfaccia) sono "distaccati". Quindi è una domanda legittima come verificare in fase di compilazione se un tipo implementa un'interfaccia.
Un modo per chiedere al compilatore di verificare che il tipo T
implementa l'interfaccia I
tentando un'assegnazione usando il valore zero per T
o puntatore a T
, a seconda dei casi. E potremmo scegliere di assegnare all'identificatore vuoto per evitare inutili immondizie:
type T struct{}
var _ I = T{} // Verify that T implements I.
var _ I = (*T)(nil) // Verify that *T implements I.
Se T
o *T
non implementa I
, sarà un errore in fase di compilazione.
Questa domanda appare anche nelle FAQ ufficiali: come posso garantire che il mio tipo soddisfi un'interfaccia?
Digitare interruttore
Gli switch di tipo possono essere utilizzati anche per ottenere una variabile che corrisponda al tipo del caso:
func convint(v interface{}) (int,error) {
switch u := v.(type) {
case int:
return u, nil
case float64:
return int(u), nil
case string:
return strconv(u)
default:
return 0, errors.New("Unsupported type")
}
}
Asserzione di tipo
È possibile accedere al tipo di dati reale dell'interfaccia con Asserzione di tipo.
interfaceVariable.(DataType)
Esempio di struct MyType
che implementa l'interfaccia Subber
:
package main
import (
"fmt"
)
type Subber interface {
Sub(a, b int) int
}
type MyType struct {
Msg string
}
//Implement method Sub(a,b int) int
func (m *MyType) Sub(a, b int) int {
m.Msg = "SUB!!!"
return a - b;
}
func main() {
var interfaceVar Subber = &MyType{}
fmt.Println(interfaceVar.Sub(6,5))
fmt.Println(interfaceVar.(*MyType).Msg)
}
Senza .(*MyType)
non saremmo in grado di accedere a Msg
Field. Se proviamo interfaceVar.Msg
mostrerà l'errore di compilazione:
interfaceVar.Msg undefined (type Subber has no field or method Msg)
Vai Interfacce da un Aspetto Matematico
In matematica, specialmente Set Theory , abbiamo una collezione di cose che è chiamata set e chiamiamo quelle cose come elementi . Mostriamo un set con il suo nome come A, B, C, ... o esplicitamente con il suo membro su notazione brace: {a, b, c, d, e}. Supponiamo di avere un elemento arbitrario x e un insieme Z, la domanda chiave è: "Come possiamo capire che x è membro di Z o no?". Matematica risposta a questa domanda con un concetto: Proprietà caratteristica di un insieme. La proprietà caratteristica di un insieme è un'espressione che descrive completamente il set. Ad esempio abbiamo impostato Natural Numbers che è {0, 1, 2, 3, 4, 5, ...}. Possiamo descrivere questo insieme con questa espressione: {a n | a 0 = 0, a n = a n-1 +1}. Nell'ultima espressione a 0 = 0, a n = a n-1 +1 è la proprietà caratteristica dell'insieme di numeri naturali. Se abbiamo questa espressione, possiamo costruire completamente questo set . Lascia che descriva l'insieme di numeri pari in questo modo. Sappiamo che questo set è composto da questi numeri: {0, 2, 4, 6, 8, 10, ...}. Con un'occhiata capiamo che tutti questi numeri sono anche un numero naturale , in altre parole se aggiungiamo alcune condizioni extra alla proprietà caratteristica dei numeri naturali, possiamo costruire una nuova espressione che descrive questo insieme . Quindi possiamo descrivere con questa espressione: {n | n è un membro di numeri naturali e il promemoria di n su 2 è zero}. Ora possiamo creare un filtro che ottiene la proprietà caratteristica di un set e filtra alcuni elementi desiderati per restituire elementi del nostro set. Ad esempio, se abbiamo un filtro di numeri naturali, entrambi i numeri naturali e quelli pari possono passare questo filtro, ma se abbiamo un filtro di numero pari, alcuni elementi come 3 e 137871 non possono passare il filtro.
La definizione dell'interfaccia in Go è come definire la proprietà caratteristica e il meccanismo di utilizzo dell'interfaccia come argomento di una funzione è come un filtro che rileva che l'elemento è un membro del set desiderato o meno. Descrivi questo aspetto con il codice:
type Number interface {
IsNumber() bool // the implementation filter "meysam" from 3.14, 2 and 3
}
type NaturalNumber interface {
Number
IsNaturalNumber() bool // the implementation filter 3.14 from 2 and 3
}
type EvenNumber interface {
NaturalNumber
IsEvenNumber() bool // the implementation filter 3 from 2
}
La proprietà caratteristica di Number
è tutte le strutture che hanno il metodo IsNumber
, per NaturalNumber
tutti quelli che hanno i metodi IsNumber
e IsNaturalNumber
e infine EvenNumber
è tutti i tipi che hanno IsNumber
, IsNaturalNumber
e IsEvenNumber
. Grazie a questa interpretazione dell'interfaccia, possiamo facilmente capire che poiché interface{}
non ha alcuna proprietà caratteristica, accetta tutti i tipi (perché non ha alcun filtro per distinguere tra valori).