接口作用
Go
語言中的接口是一種類型,類似於Python
中的抽象基類。
Go
語言中使用接口來體現多態,是duck-type
的一種體現。
如,只要一個東西會叫,會走,那么我們就可以將它定義為一個動物的接口。
接口定義
Go
中提倡面向接口編程,以下是接口的定義。
type 接口類型名 interface{
方法名1( 參數列表1 ) 返回值列表1
方法名2( 參數列表2 ) 返回值列表2
…
}
關於接口的定義有以下幾點注意事項:
接口名在單詞后面一般都需添加er的后綴,代表這是一個接口
當接口名與方法名都是大寫時,代表該接口與方法均可被外部包進行訪問
參數列表以及返回值列表參數變量名可以省略
以下我們將定義一個動物的接口,會叫,會移動我們將將它看作為動物。
並且我們為該接口定義了一個方法撒潑,只要是動物就可以進行撒潑。
package main
import (
"fmt"
)
type animal interface {
move()
roar()
}
func sapo(a animal){ // 接收一個動物類型
a.move()
a.roar()
}
接口使用
如何使用上面的接口呢?其實我們還需要做結構體。
如下示例,做了一個狗的結構體和一個狼的結構體並且進行實例化,由於狗和狼都實現了move
以及roar
方法,所以這兩個實例化都可以看作是animal
類型,即可以調用sapo
方法。
package main
import (
"fmt"
)
type animal interface {
move()
roar()
}
func sapo(a animal){
a.move()
a.roar()
}
type dog struct {
name string
}
func (d dog) move() {
fmt.Printf("%s在移動\n", d.name)
}
func (d dog) roar() {
fmt.Printf("%s在吼叫\n", d.name)
}
type wolf struct {
name string
}
func (w wolf) move() {
fmt.Printf("%s在移動\n", w.name)
}
func (w wolf) roar() {
fmt.Printf("%s在吼叫\n", w.name)
}
func main() {
d1 := dog{
name: "大黃",
}
w1 := wolf{
name: "灰太狼",
}
sapo(d1) // 大黃調用動物的撒潑方法,由於會動會叫就是動物,所以大黃有兩種類型,一種是動物,一種是狗
sapo(w1)
}
// 大黃在移動
// 大黃在吼叫
// 灰太狼在移動
// 灰太狼在吼叫
接口變量
接口是一種類型,所以一個變量可以定義為該類型。
如下所示,聲明了一個動物類型的接口變量,當一個結構體實例對象擁有了move
與roar
方法后,才可成功進行賦值。
package main
import (
"fmt"
)
type animal interface {
move()
roar()
}
type dog struct {
name string
}
func (d dog) move() {
fmt.Printf("%s在移動\n", d.name)
}
func (d dog) roar() {
fmt.Printf("%s在吼叫\n", d.name)
}
func main() {
var a1 animal // 動物類型
d1 := dog{
name: "大黃",
}
a1 = d1 // 由於大黃有move與roar方法,所以它也算是動物類型。因此可以賦值成功
a1.move()
}
結構體方法類型
結構體不同的方法類型,會對接口產生不同的影響。
值接收者方法
當結構體的方法是值接收者方法時,該結構體實例化可以賦值給對應的接口變量,並且是任意形式。
package main
import (
"fmt"
)
type people interface {
combHair() // 能梳頭就是人
}
type person struct {
name string
}
// 值接收者方法
func (p person) combHair() {
fmt.Printf("%s在梳頭發", p.name)
}
func main() {
var pe people // 人類
var p1 = person{
name: "雲崖",
}
// 全部都可以做為人類
pe = p1
pe = &p1
pe = *(&p1)
fmt.Println(pe)
}
指針接收者方法
當結構體的方法是指針接收者方法時,該結構體實例化賦值給接口變量時只能是地址傳遞。
package main
import (
"fmt"
)
type people interface {
combHair() // 能梳頭就是人
}
type person struct {
name string
}
// 指針接收者方法
func (p *person) combHair() {
fmt.Printf("%s在梳頭發", p.name)
}
func main() {
var pe people // 人類
var p1 = person{
name: "雲崖",
}
pe = *p1 // 錯誤
pe = &p1 // 只能傳遞地址
pe = *(&p1) // 錯誤
fmt.Println(pe)
}
類型與接口
一個類型多個接口
如會梳頭發是人類,會移動是動物類。
那么我們就可以對person
這個結構體做兩個接口,一個是人類的接口,一個是動物類的接口。
person
實例化對象p1
同時滿足人類和動物這兩個接口。
package main
import (
"fmt"
)
type people interface {
combHair() // 能梳頭就是人
}
type animal interface {
move() // 能移動就是動物
}
type person struct {
name string
}
func (p person) move() {
fmt.Println("人移動了")
}
func (p person) combHair(){
fmt.Println("人在梳頭發")
}
func main() {
var pe people // 人類接口變量
var an animal // 動物接口變量
var p1 = person{ // 實例化出一個人
name: "雲崖",
}
pe = p1
an = p1
fmt.Println(pe)
fmt.Println(an)
}
一個接口多個類型
如會動的都是動物,那么下面例子中狗和牛等結構體的實例化就都是動物。
這就是一個動物接口可以有多個類型的體現。
package main
import (
"fmt"
)
type people interface {
combHair() // 能梳頭就是人
}
type animal interface {
move() // 能移動就是動物
}
type dog struct {
name string
}
type cattle struct {
name string
}
func (d dog) move() {
fmt.Println("狗在動")
}
func (c cattle) move() {
fmt.Println("牛在動")
}
func main() {
var an animal // 動物接口變量
d1 := dog{
name: "大黃",
}
c1 := cattle{
name: "牛魔王",
}
an = d1
fmt.Println(an)
an = c1
fmt.Println(an)
}
接口嵌套
比如兩棲動物可以在水中生活,也可以在陸地生活。
我們定義一個陸地生活動物的接口,再定義一個水中生活動物的接口。
如何表示兩棲動物的接口呢?只需要應用嵌套讓兩棲動物的接口同時擁有陸地生活動物接口的方法和水中生活動物的方法即可。
如下示例:
package main
import (
"fmt"
)
type terrestrialAnimal interface {
landMobile() // 陸地移動,則是陸生動物
}
type aquatic interface {
swim() // 能夠游泳,則是水生動物
}
type amphibian interface {
terrestrialAnimal
aquatic
// 同時實現了陸生動物和水生動物的特性,則是兩棲動物
}
type frog struct{
name string
// 青蛙結構體
}
// 青蛙會游泳
func (f frog) swim(){
fmt.Println("青蛙在游泳")
}
// 青蛙能上岸
func (f frog) landMobile(){
fmt.Println("青蛙在陸地上歡快的蹦躂")
}
func main() {
var amp amphibian // 兩棲動物接口變量
var f1 = frog {
name : "呱呱娃",
}
amp = f1 // 青蛙是兩棲動物
fmt.Println(amp)
}
空接口
空接口在實際開發中使用非常廣泛,它代表可以是任意類型的變量。
接口定義
使用interface{}
來定義一個空接口。
如下所示:
package main
import (
"fmt"
)
func main(){
var arbitraryType interface{} // 接收任意類型的變量
var str = "HELLO,WORLD"
arbitraryType = str
fmt.Printf("%T \n", arbitraryType) // string
var num = int64(100)
arbitraryType = num
fmt.Printf("%T \n",arbitraryType) // int64
}
實際應用
空接口一般應用於函數形參,以及map
值中。
如下,該函數允許傳遞任何數據類型的數據。
package main
import (
"fmt"
)
func test(v1 interface{}) {
fmt.Printf("%T \n", v1) // int
}
func main() {
test(100)
}
其實更多的應用場景在map
中,如下定義的map
值可以是任意類型,這樣存取都會變得很方便。
package main
import (
"fmt"
)
var m = make(map[string]interface{}, 30)
func main() {
m["k1"] = "第一個"
m["k2"] = 2
m["k3"] = []int16{1,2,3,4}
fmt.Print(m)
}
類型斷言
由於interface{}
空接口可以存儲任意類型的值,那么我們如何判斷出值的類型?
方法如下:
x.(T)
x:表示類型為interface{}的變量
T:表示斷言x可能是的類型。
方法斷言的作用其實就類似於猜,這種用的不是很多。
如下示例,使用switch
進行類型斷言的判斷:
package main
import (
"fmt"
)
var m = make(map[string]interface{}, 30)
func test(v1 map[string]interface{}) {
for _, value := range v1 {
switch value.(type) { // 使用斷言獲取到類型
case string:
fmt.Println("這是一個字符串類型", value)
case int:
fmt.Println("這是一個int類型", value)
case []int16:
fmt.Println("這是一個int16的切片類型", value)
default:
fmt.Println("未識別的類型", value)
}
}
}
func main() {
m["k1"] = "第一個"
m["k2"] = 2
m["k3"] = []int16{1, 2, 3, 4}
test(m)
}