Interface 是Go語言里很優(yōu)秀的一個設(shè)計展姐,它本身是僅僅是一個結(jié)構(gòu)體殃饿,但是通過interface我們可以實現(xiàn)面向?qū)ο蟮暮芏嗵匦裕热缍鄳B(tài)息裸、動態(tài)聯(lián)編等。interface 在Go 語言里使用起來的感覺沪编,相比于其它語言也要優(yōu)雅得多呼盆。
底層實現(xiàn)
Interface 的底層只是一個簡單的由c語言實現(xiàn)的數(shù)據(jù)結(jié)構(gòu):
struct Eface
{
Type* type;
void* data;
};
type 表示它的類型,data 里存儲了它的具體信息蚁廓。對于Go語言访圃,任何數(shù)據(jù)類型都是可以由interface{}來表示的。當我們使用時可以通過反射把里面的信息取出來相嵌。
方法實現(xiàn)
我們先定義一個簡單的接口腿时,類似于Java 中的那樣。
type Animal interface {
Run()
Roar()
}
定義一個結(jié)構(gòu)體 Dog 饭宾,直接定義Run 和 Roar 方法批糟,即可實現(xiàn)接口。
type Dog struct {
Name string
Size string
}
func (d *Dog)Run() {
fmt.Printf("Dog %s run!\n", d.Name)
}
func (d *Dog)Roar() {
fmt.Printf("A %s dog roar!\n", d.Size)
}
聲明一個Dog 類型并初始化看铆。
func main () {
d := Dog{
Name: "Jim",
Size: "big",
}
d.Run()
d.Roar()
}
// output:
// Dog Jim run!
// A big dog roar!
這樣實現(xiàn)一個接口的方法很簡單徽鼎,只需要在定義某一類型的時候,定義一個同名的方法即可。
多態(tài)
我們可以直接聲明一個該接口的變量否淤,再讓這個變量指向?qū)崿F(xiàn)其方法類型的實例的地址悄但,這個變量就可以調(diào)用此實例的方法。
func main() {
...
var a Animal
a = &d
a.Run()
a.Roar()
}
// output:
// Dog Jim run!
// A big dog roar!
同樣定義另外一個實現(xiàn)該接口的類型石抡。
type Cat struct {
Name string
Color string
}
func (c *Cat)Run() {
fmt.Printf("Cat %s run!\n", c.Name)
}
func (c *Cat)Roar() {
fmt.Printf("A %s cat roar!\n", c.Color)
}
再讓上面聲明的接口變量指向這個類型的實例的地址檐嚣,實際上調(diào)用方法的對象則變成后者。
func main() {
...
c := Cat{
Name: "Kitty",
Color: "yello",
}
a = &c
c.Roar()
c.Run()
}
//output:
// A yello cat roar!
// Cat Kitty run!
Go語言里面向?qū)ο蟮奶匦酝ㄟ^interface 很輕易地體現(xiàn)了出來汁雷。本質(zhì)上上述聲明的Animal 變量a 是一個指針净嘀,它底層的內(nèi)存結(jié)構(gòu)包含兩個字段:
- receiver,a可以指向任意變量侠讯,只需要這個變量的類型實現(xiàn)了該接口挖藏,此時receiver 存儲該變量的地址,實際上此地址指向的其實是該變量的一個副本厢漩,因為Go 會在堆上申請一段空間用以存儲變量的副本膜眠。如果該變量不大于一個字,則該變量直接存儲在receiver 中溜嗜。
- method table ptr宵膨,類似于C++ 里的虛函數(shù)表,當a 指向某實例變量后炸宵,該字段存儲其方法的入口地址辟躏。
類型轉(zhuǎn)換
一個空的 interface ,不包含任何方法土全,因此它能夠存儲任意類型的值捎琐。
func main() {
var i interface{}
a := 1
i = a
fmt.Println(i)
b := true
i = b
fmt.Println(i)
}
//output:
// 1
// true
我們可以借此實現(xiàn)一個函數(shù),它的參數(shù)可以接收任意類型的值裹匙。這時瑞凑,interface 的用法就比較像Java里的Object 對象。同樣概页,我們使用這個參數(shù)前需要類型轉(zhuǎn)換籽御,在Java里可以通過運算符instanceof 來判斷一個對象的實例是否為某一類型,并可以通過顯示類型轉(zhuǎn)換來使用這個實例惰匙。 在Go語言里技掏,我們通過斷言一樣可以做到這些。先看看斷言的用法:
func testFunc(animal interface{}) {
if c, ok := animal.(Cat); ok {
fmt.Println(c)
} else {
fmt.println("It's not a cat!")
}
}
格式為:變量名.(類型名)项鬼。返回兩個值零截,第一個值為轉(zhuǎn)換類型后的實例,第二個值為布爾變量秃臣,如果傳入的參數(shù)為Cat 類型就返回 true, 否則返回 false 。 調(diào)用testFunc 時,在函數(shù)里判斷其傳入的參數(shù)是否為Cat 類型奥此,如果是就打印出它的值弧哎。
也可以直接使用斷言返回的第一個值。用法如c := animal.(Cat)稚虎,直接忽略判斷的結(jié)果撤嫩。下面我們?yōu)閠estFunc 加入更多類型的判斷,這時蠢终,通過結(jié)合switch的使用序攘,使代碼可讀性更加友好。
func testFunc(animal interface{}) {
switch animal.(type) {
case Dog:
fmt.Println(animal)
case Cat:
fmt.Println(animal)
default:
fmt.Println("It's nothing!")
}
}