Go 介面型別

雲崖先生發表於2020-10-08

介面作用

   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)
}

// 大黃在移動
// 大黃在吼叫
// 灰太狼在移動
// 灰太狼在吼叫

介面變數

   介面是一種型別,所以一個變數可以定義為該型別。

   如下所示,宣告瞭一個動物型別的介面變數,當一個結構體例項物件擁有了moveroar方法後,才可成功進行賦值。

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)
}

相關文章