Go
Interfejsy
Szukaj…
Uwagi
Interfejsy w Go to tylko stałe zestawy metod. Typ domyślnie implementuje interfejs, jeśli jego zestaw metod jest nadzbiorem interfejsu. Nie ma deklaracji woli.
Prosty interfejs
W Go interfejs jest tylko zbiorem metod. Używamy interfejsu, aby określić zachowanie danego obiektu.
type Painter interface {
Paint()
}
Typ implementacji nie musi deklarować, że implementuje interfejs. Wystarczy zdefiniować metody o tym samym podpisie.
type Rembrandt struct{}
func (r Rembrandt) Paint() {
// use a lot of canvas here
}
Teraz możemy użyć struktury jako interfejsu.
var p Painter
p = Rembrandt{}
Interfejs może być spełniony (lub zaimplementowany) przez dowolną liczbę typów. Typ może również implementować dowolną liczbę interfejsów.
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")
}
Tutaj struktura Human
spełnia zarówno interfejs Singer
i Writer
, ale struktura OnlySinger
spełnia tylko interfejs Singer
.
Pusty interfejs
Istnieje pusty typ interfejsu, który nie zawiera żadnych metod. Deklarujemy to jako interface{}
. Nie zawiera żadnych metod, więc każdy type
go spełnia. Dlatego pusty interfejs może zawierać dowolną wartość typu.
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"}
Najczęstszym przypadkiem użycia interfejsów jest zapewnienie, że zmienna obsługuje jedno lub więcej zachowań. Natomiast podstawowym przypadkiem użycia pustego interfejsu jest zdefiniowanie zmiennej, która może przechowywać dowolną wartość, niezależnie od jej konkretnego typu.
Aby odzyskać te wartości jako ich pierwotne typy, musimy tylko to zrobić
i = a.(int)
s = a.(string)
m := a.(*StructType)
lub
i, ok := a.(int)
s, ok := a.(string)
m, ok := a.(*StructType)
ok
wskazuje, czy interface a
można przekształcić na dany typ. Jeśli nie można rzucić, ok
będzie false
.
Wartości interfejsów
Jeśli zadeklarujesz zmienną interfejsu, może ona przechowywać dowolny typ wartości, który implementuje metody zadeklarowane przez interfejs!
Jeśli zadeklarujemy h
interface Singer
, może przechowywać wartość typu Human
lub OnlySinger.
Wynika to z faktu, że wszystkie implementują metody określone przez interfejs Singer
.
var h Singer
h = &human{}
h.Sing()
Określanie typu bazowego z interfejsu
Czasami przydatne może być sprawdzenie, który typ bazowy został zaliczony. Można to zrobić za pomocą przełącznika typu. Zakłada się, że mamy dwie struktury:
type Rembrandt struct{} func (r Rembrandt) Paint() {} type Picasso struct{} func (r Picasso) Paint() {}
Implementujące interfejs Painter:
type Painter interface { Paint() }
Następnie możemy użyć tego przełącznika, aby określić podstawowy typ:
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") } }
Sprawdzanie czasu kompilacji, czy typ spełnia interfejs
Interfejsy i implementacje (typy implementujące interfejs) są „odłączone”. Jest więc słuszne pytanie, jak sprawdzić w czasie kompilacji, czy typ implementuje interfejs.
Jednym ze sposobów poproszenia kompilatora o sprawdzenie, czy typ T
implementuje interfejs I
jest próba przypisania przy użyciu wartości zerowej dla T
lub wskaźnika do T
, odpowiednio. Możemy też przypisać pusty identyfikator, aby uniknąć niepotrzebnych śmieci:
type T struct{}
var _ I = T{} // Verify that T implements I.
var _ I = (*T)(nil) // Verify that *T implements I.
Jeśli T
lub *T
nie implementują I
, będzie to błąd czasu kompilacji.
To pytanie pojawia się również w oficjalnym FAQ: Jak mogę zagwarantować, że mój typ spełnia interfejs?
Przełącznik typu
Przełączników typów można również użyć do uzyskania zmiennej pasującej do typu sprawy:
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")
}
}
Wpisz Asercja
Możesz uzyskać dostęp do prawdziwego typu danych interfejsu za pomocą Type Assertion.
interfaceVariable.(DataType)
Przykład struktury MyType
implementującej interfejs 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)
}
Bez .(*MyType)
nie moglibyśmy uzyskać dostępu do Msg
Field. Jeśli spróbujemy interfaceVar.Msg
, wyświetli się błąd kompilacji:
interfaceVar.Msg undefined (type Subber has no field or method Msg)
Interfejsy Go z aspektu matematycznego
W matematyce, zwłaszcza w teorii mnogości , mamy zbiór rzeczy, które nazywamy zestawem i nazywamy te rzeczy elementami . Pokazujemy zestaw z jego nazwami jak A, B, C, ... lub jawnie z umieszczeniem jego elementu na notacji nawiasowej: {a, b, c, d, e}. Załóżmy, że mamy dowolny element x i zbiór Z. Kluczowe pytanie brzmi: „Jak możemy zrozumieć, że x jest członkiem Z, czy nie?”. Matematyczka udzieli odpowiedzi na to pytanie z koncepcją: Właściwość charakterystyczna zbioru. Charakterystyczna właściwość zestawu jest wyrażeniem, które całkowicie opisuje zestaw. Na przykład mamy zestaw liczb naturalnych, który wynosi {0, 1, 2, 3, 4, 5, ...}. Możemy opisać ten zestaw za pomocą tego wyrażenia: {a n | a 0 = 0, a n = a n-1 +1}. W ostatnim wyrażeniu a 0 = 0, a n = a n-1 +1 jest charakterystyczną właściwością zbioru liczb naturalnych. Jeśli mamy to wyrażenie, możemy całkowicie zbudować ten zestaw . Opiszmy w ten sposób zestaw liczb parzystych . Wiemy, że ten zestaw składa się z następujących liczb: {0, 2, 4, 6, 8, 10, ...}. Na pierwszy rzut oka rozumiemy, że wszystkie te liczby są również liczbami naturalnymi , innymi słowy, jeśli dodamy dodatkowe warunki do charakterystycznej właściwości liczb naturalnych, możemy zbudować nowe wyrażenie opisujące ten zestaw . Możemy więc opisać za pomocą tego wyrażenia: {n | n jest członkiem liczb naturalnych, a przypomnienie n na 2 wynosi zero}. Teraz możemy utworzyć filtr, który uzyskuje charakterystyczną właściwość zestawu i filtruje wybrane elementy, aby zwrócić elementy naszego zestawu. Na przykład, jeśli mamy filtr liczb naturalnych, zarówno liczby naturalne, jak i liczby parzyste mogą przejść przez ten filtr, ale jeśli mamy filtr liczb parzystych, niektóre elementy, takie jak 3 i 137871, nie mogą przejść przez filtr.
Definicja interfejsu w Go jest jak definiowanie właściwości charakterystycznej, a mechanizm używania interfejsu jako argumentu funkcji jest jak filtr, który wykrywa, że element jest członkiem naszego pożądanego zestawu lub nie. Opiszmy ten aspekt kodem:
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
}
Charakterystyczną właściwością Number
są wszystkie struktury, które mają metodę IsNumber
, dla NaturalNumber
są wszystkie te, które mają metody IsNumber
i IsNaturalNumber
, a ostatecznie dla EvenNumber
są wszystkie typy, które mają IsNumber
, IsNaturalNumber
i IsEvenNumber
. Dzięki tej interpretacji interfejsu z łatwością możemy zrozumieć, że ponieważ interface{}
nie ma żadnej charakterystycznej właściwości, zaakceptuj wszystkie typy (ponieważ nie ma żadnego filtru do rozróżniania wartości).