Go
gränssnitt
Sök…
Anmärkningar
Gränssnitt i Go är bara fasta metoduppsättningar. En typ implementerar implicit ett gränssnitt om dess metoduppsättning är en superset av gränssnittet. Det finns ingen avsiktsförklaring.
Enkelt gränssnitt
I Go är ett gränssnitt bara en uppsättning metoder. Vi använder ett gränssnitt för att specificera ett beteende hos ett visst objekt.
type Painter interface {
Paint()
}
Implementeringstypen behöver inte förklara att den implementerar gränssnittet. Det räcker med att definiera metoder för samma signatur.
type Rembrandt struct{}
func (r Rembrandt) Paint() {
// use a lot of canvas here
}
Nu kan vi använda strukturen som gränssnitt.
var p Painter
p = Rembrandt{}
Ett gränssnitt kan tillfredsställas (eller implementeras) av ett godtyckligt antal typer. En typ kan också implementera ett godtyckligt antal gränssnitt.
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")
}
Här uppfyller The Human
struct både Singer
och Writer
gränssnittet, men OnlySinger
strukturen uppfyller endast Singer
gränssnittet.
Tomt gränssnitt
Det finns en tom gränssnitttyp som inte innehåller några metoder. Vi förklarar det som interface{}
. Detta innehåller inga metoder så alla type
uppfyller det. Därför kan det tomma gränssnittet innehålla valfritt typvärde.
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"}
Det vanligaste fallet för gränssnitt är att se till att en variabel stöder ett eller flera beteenden. Däremot är det primära användningsfallet för det tomma gränssnittet att definiera en variabel som kan innehålla valfritt värde, oavsett dess konkreta typ.
För att få tillbaka dessa värden som sina ursprungliga typer behöver vi bara göra
i = a.(int)
s = a.(string)
m := a.(*StructType)
eller
i, ok := a.(int)
s, ok := a.(string)
m, ok := a.(*StructType)
ok
anger om interface a
är konvertibelt till en viss typ. Om det inte är möjligt att kasta ok
kommer det att vara false
.
Gränssnittsvärden
Om du deklarerar en variabel i ett gränssnitt kan det lagra valfri värdetyp som implementerar de metoder som deklareras av gränssnittet!
Om vi förklarar h
för interface Singer
, kan det lagra ett värde av typen Human
eller OnlySinger.
Detta beror på att alla implementerar metoder som anges av Singer
gränssnittet.
var h Singer
h = &human{}
h.Sing()
Bestämma underliggande typ från gränssnittet
I farten kan det ibland vara användbart att veta vilken underliggande typ du har passerat. Detta kan göras med en typomkopplare. Detta förutsätter att vi har två strukturer:
type Rembrandt struct{} func (r Rembrandt) Paint() {} type Picasso struct{} func (r Picasso) Paint() {}
Som implementerar Painter-gränssnittet:
type Painter interface { Paint() }
Sedan kan vi använda denna switch för att bestämma den underliggande typen:
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") } }
Samla tidskontroll om en typ uppfyller ett gränssnitt
Gränssnitt och implementationer (typer som implementerar ett gränssnitt) "lossas". Så det är en rättslig fråga hur man kontrollerar vid sammanställningstid om en typ implementerar ett gränssnitt.
Ett sätt att be kompilatorn kontrollera att typen T
implementerar gränssnittet I
är genom att försöka en tilldelning med nollvärdet för T
eller pekaren till T
, efter behov. Och vi kan välja att tilldela den tomma identifieraren för att undvika onödigt skräp:
type T struct{}
var _ I = T{} // Verify that T implements I.
var _ I = (*T)(nil) // Verify that *T implements I.
Om T
eller *T
inte implementerar I
kommer det att vara ett kompileringstidsfel.
Denna fråga visas också i den officiella FAQ: Hur kan jag garantera att min typ uppfyller ett gränssnitt?
Skriv omkopplare
Typomkopplare kan också användas för att få en variabel som matchar fallet:
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")
}
}
Skriv påstående
Du kan få åtkomst till den verkliga datatypen för gränssnittet med typpåståendet.
interfaceVariable.(DataType)
Exempel på MyType
som implementerar gränssnitt 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)
}
Utan .(*MyType)
skulle vi inte komma åt Msg
fältet. Om vi försöker interfaceVar.Msg
kommer det att visa kompileringsfel:
interfaceVar.Msg undefined (type Subber has no field or method Msg)
Gå gränssnitt från en matematisk aspekt
I matematik, särskilt Set Theory , har vi en samling saker som kallas set och vi benämner dessa saker som element . Vi visar en uppsättning med dess namn som A, B, C, ... eller uttryckligen med att sätta sin medlem på brace-notation: {a, b, c, d, e}. Anta att vi har ett godtyckligt element x och en uppsättning Z, nyckelfrågan är: "Hur kan vi förstå att x är medlem i Z eller inte?". Matematiker svarar på denna fråga med ett koncept: Karakteristisk egenskap hos en uppsättning. Karakteristisk egenskap hos en uppsättning är ett uttryck som beskriver uppsättningen fullständigt. Vi har till exempel uppsättning av naturliga nummer som är {0, 1, 2, 3, 4, 5, ...}. Vi kan beskriva denna uppsättning med detta uttryck: {a n | a 0 = 0, a n = a n-1 +1}. I sista uttrycket är 0 = 0, a n = a n-1 +1 den karakteristiska egenskapen för uppsättning av naturliga nummer. Om vi har detta uttryck kan vi bygga denna uppsättning helt . Låt oss beskriva uppsättningen med jämna siffror på detta sätt. Vi vet att denna uppsättning görs av dessa siffror: {0, 2, 4, 6, 8, 10, ...}. Med en överblick förstår vi att allt detta nummer också är ett naturligt tal , med andra ord om vi lägger till några extra villkor till karakteristiska egenskaper hos naturliga nummer, kan vi bygga ett nytt uttryck som beskriver denna uppsättning . Så vi kan beskriva med detta uttryck: {n | n är en medlem av naturliga siffror och påminnelsen om n på 2 är noll}. Nu kan vi skapa ett filter som får en sängs karakteristiska egenskap och filtrera några önskade element för att returnera element i vår uppsättning. Om vi till exempel har ett naturligt talfilter kan både naturliga nummer och jämna nummer passera det här filtret, men om vi har ett jämnt talfilter kan vissa element som 3 och 137871 inte passera filtret.
Definition av gränssnitt i Go är som att definiera den karakteristiska egenskapen och mekanismen för att använda gränssnittet som ett argument för en funktion är som ett filter som upptäcker att elementet är medlem i vår önskade uppsättning eller inte. Låt oss beskriva denna aspekt med kod:
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
}
Den karakteristiska egenskapen till Number
är alla strukturer som har IsNumber
metoden, för NaturalNumber
är alla de som har IsNumber
och IsNaturalNumber
metoder och slutligen för EvenNumber
är alla typer som har IsNumber
, IsNaturalNumber
och IsEvenNumber
metoder. Tack vare denna tolkning av gränssnittet kan vi lätt förstå att eftersom interface{}
inte har någon karakteristisk egenskap, accepterar alla typer (eftersom det inte har något filter för att skilja mellan värden).