通過(guò)關(guān)鍵字type和interface,我們可以聲明出接口類(lèi)型。接口類(lèi)型的類(lèi)型字面量與結(jié)構(gòu)體類(lèi)型的看起來(lái)有些相似,它們都用花括號(hào)包裹一些核心信息。只不過(guò),結(jié)構(gòu)體類(lèi)型包裹的是它的字段聲明,而接口類(lèi)型包裹的是它的方法定義。
公司主營(yíng)業(yè)務(wù):網(wǎng)站建設(shè)、成都做網(wǎng)站、移動(dòng)網(wǎng)站開(kāi)發(fā)等業(yè)務(wù)。幫助企業(yè)客戶真正實(shí)現(xiàn)互聯(lián)網(wǎng)宣傳,提高企業(yè)的競(jìng)爭(zhēng)能力。創(chuàng)新互聯(lián)是一支青春激揚(yáng)、勤奮敬業(yè)、活力青春激揚(yáng)、勤奮敬業(yè)、活力澎湃、和諧高效的團(tuán)隊(duì)。公司秉承以“開(kāi)放、自由、嚴(yán)謹(jǐn)、自律”為核心的企業(yè)文化,感謝他們對(duì)我們的高要求,感謝他們從不同領(lǐng)域給我們帶來(lái)的挑戰(zhàn),讓我們激情的團(tuán)隊(duì)有機(jī)會(huì)用頭腦與智慧不斷的給客戶帶來(lái)驚喜。創(chuàng)新互聯(lián)推出朔州免費(fèi)做網(wǎng)站回饋大家。
接口類(lèi)型聲明中的這些方法所代表的就是該接口的方法集合。一個(gè)接口的方法集合就是它的全部特征。對(duì)于任何數(shù)據(jù)類(lèi)型,只要它的方法集合中完全包含了一個(gè)接口的全部特征(即全部的方法),那么它就一定是這個(gè)接口的實(shí)現(xiàn)類(lèi)型:
type Pet interface {
SetName(name string)
Name() string
Category() string
}
這里聲明了一個(gè)接口類(lèi)型Pet,它包含3個(gè)方法定義。這3個(gè)方法共同組成了接口類(lèi)型Pet的方法集合。只要一個(gè)數(shù)據(jù)類(lèi)型的方法集合中有3個(gè)方法,那么它就就一定是Pet接口類(lèi)型的實(shí)現(xiàn)。這是一種無(wú)浸入式的接口實(shí)現(xiàn)方式。這種方式還有一個(gè)專(zhuān)有名詞,叫“Duck typing”,中文常譯作“鴨子類(lèi)型”。
下面的是上一篇結(jié)尾的那個(gè)例子,不過(guò)Cat換成了Dog:
package main
import "fmt"
type Pet interface {
SetName(name string)
Name() string
Category() string
}
type Dog struct {
name string // 名字。
}
func (dog *Dog) SetName(name string) {
dog.name = name
}
func (dog Dog) Name() string {
return dog.name
}
func (dog Dog) Category() string {
return "dog"
}
func main() {
// 示例1。
dog := Dog{"little pig"}
_, ok := interface{}(dog).(Pet)
fmt.Printf("Dog implements interface Pet: %v\n", ok)
_, ok = interface{}(&dog).(Pet)
fmt.Printf("*Dog implements interface Pet: %v\n", ok)
fmt.Println()
// 示例2。
var pet Pet = &dog
fmt.Printf("This pet is a %s, the name is %q.\n",
pet.Category(), pet.Name())
}
聲明的Dog有3個(gè)方法,其中2個(gè)是值方法Name和Category,還有一個(gè)指針?lè)椒⊿etName。Dog類(lèi)型本身的方法集合中只有2個(gè)方法,就是所有的值方法。而它的指針類(lèi)型*Dog方法集合包含了3個(gè)方法,就是它擁有Dog類(lèi)型附帶的所有值方法和指針?lè)椒?。而這3個(gè)方法正好是Pet接口中,所以*Dog類(lèi)型就成為了Pet接口的實(shí)現(xiàn)類(lèi)型。
在上面,示例2的那一小段代碼,把main主函數(shù)開(kāi)頭聲明的Dog類(lèi)型的變量dog,把它的指針賦值給了類(lèi)型為Pet的變量pet。這里的變量pet的值,可以被叫做它的實(shí)際值(也稱(chēng)動(dòng)態(tài)值)。該值的類(lèi)型可以被叫做這個(gè)表量的實(shí)際類(lèi)型(也稱(chēng)動(dòng)態(tài)類(lèi)型)。
動(dòng)態(tài)類(lèi)型的叫法是相對(duì)于靜態(tài)類(lèi)型而言的。對(duì)于變量pet,它的靜態(tài)類(lèi)型就是Pet,并且不會(huì)改變。但是他的動(dòng)態(tài)會(huì)隨著賦給他的動(dòng)態(tài)值而變化。這里的動(dòng)態(tài)類(lèi)型是*Dog類(lèi)型,而動(dòng)態(tài)值就是&dog的值(就是dog的地址)。
下面的示例定義了簡(jiǎn)單的結(jié)構(gòu)體和接口類(lèi)型:
package main
import "fmt"
type Pet interface {
Name() string
}
type Dog struct {
name string
}
// 如果這是一個(gè)值方法?
func (d *Dog) SetName (name string) {
d.name = name
}
func (d Dog) Name() string {
return d.name
}
func main() {
dog := Dog{"Snoopy"}
fmt.Println(dog.Name())
var pet Pet = dog // 這個(gè)如果是一個(gè)取址表達(dá)式?
dog.SetName("Goofy ")
fmt.Println(dog.Name())
fmt.Println(pet.Name())
}
這里的SetName方法必須是指針?lè)椒āR驗(yàn)槿绻侵捣椒?,接受者就是dog的副本,該方法改變的也只是副本的name的值,不會(huì)影響的dog變量本身。如果是指針?lè)椒?,那么?dāng)SetName方法執(zhí)行后,dog的name字典就就被改變了。
然后接著看接下來(lái)的一層。dog賦值給了pet,然后dog的name字段確實(shí)變了,但是這里pet里還是原來(lái)的值。這里的原因和上面的一樣的。如果使用一個(gè)變量給另外一個(gè)變量賦值,那么真正賦值給后者的,其實(shí)是一個(gè)副本。這里如果是把&dog賦值給pet,那么pet的值也就會(huì)跟著dog的進(jìn)行變化了。
上面可以這么理解,但是嚴(yán)格來(lái)講,即使像前面那樣把dog的值賦給了pet,pet的值與dog的值也是不同的。在給一個(gè)接口變量賦值的時(shí)候,該變量的動(dòng)態(tài)類(lèi)型會(huì)與它的動(dòng)態(tài)值一起被存儲(chǔ)在一個(gè)專(zhuān)用的數(shù)據(jù)結(jié)構(gòu)中。無(wú)論從存儲(chǔ)的內(nèi)容還是存儲(chǔ)的結(jié)構(gòu)來(lái)看,pet的值與dog的值都是不同的。不過(guò)可以認(rèn)為,此時(shí)的pet的值中包含了dog的值的副本。
這里要討論的是接口變量在聲明情況下才真正為nil:
package main
import "fmt"
type Pet interface {
Name() string
}
type Dog struct {
name string
}
func (d Dog) Name() string {
return d.name
}
func main() {
var dog *Dog
fmt.Println(dog)
fmt.Println(dog == nil) // true
var pet Pet = dog
// var pet Pet = nil // 注釋掉上面的,試試這句
fmt.Println(pet)
fmt.Println(pet == nil) // false
// fmt.Printf("%T", pet) // 打印動(dòng)態(tài)類(lèi)型
這里先聲明了一個(gè)*Dog類(lèi)型的變量dog,并沒(méi)有對(duì)他進(jìn)行初始化。所以它的值就是nil。
然后把dog賦值給了接口類(lèi)型pet,此時(shí)判斷pet是否為nil時(shí)返回的false。
這里確實(shí)把值為nil的dog變量賦值給了pet。pet的動(dòng)態(tài)值也確實(shí)是nil,但是pet的值不是nil。動(dòng)態(tài)值只是pet值的一部分,還有動(dòng)態(tài)類(lèi)型。pet的動(dòng)態(tài)類(lèi)型是*Dog,可以通過(guò)fmt.Printf函數(shù)和占位符%T打印變量的類(lèi)型。另外reflect包的TypeOf函數(shù)也可以起到類(lèi)似的作用。
如果把nil直接賦值給pet的話,那么pet就是真正的nil了。在Go語(yǔ)言里,字面量nil表示的值叫做無(wú)類(lèi)型的nil。這個(gè)是真正的nil,因?yàn)樗念?lèi)型也是nil。而例子中,雖然dog的值是nil,但是當(dāng)把這個(gè)變量賦值pet的時(shí)候,其實(shí)是賦值給pet的值是一個(gè)*Dog類(lèi)型的nil值。對(duì)于接口變量,需要?jiǎng)討B(tài)值和動(dòng)態(tài)類(lèi)型都是nil,那才是真正的nil。要想讓一個(gè)接口變量的值真正為nil,可以把一個(gè)字面的nil賦值給它,或者只聲明接口而不做初始化也可以。
接口類(lèi)型的間的嵌入也被稱(chēng)為接口的組合。組合的接口之間不能有同名的方法存在,如果有同名的方法就會(huì)產(chǎn)生沖突而無(wú)法通過(guò)編譯。與結(jié)構(gòu)體類(lèi)型間的嵌入很相似,只要把一個(gè)接口類(lèi)型的名稱(chēng)直接寫(xiě)的另一個(gè)接口類(lèi)型的成員列表中就可以進(jìn)行組合:
type Animal interface {
ScientificName() string
Category() string
}
type Pet interface {
Animal
Name() string
}
組合后,Animal接口包含的所有方法也就是成為了Pet接口的方法。
Go語(yǔ)言團(tuán)隊(duì)鼓勵(lì)我們聲明體量較小的接口,并建議我們通過(guò)這種接口間的組合來(lái)擴(kuò)展程序、增加程序的靈活性。相比于包含很多方法的大接口而言,小接口可以更加專(zhuān)注地表達(dá)某一種能力或某一類(lèi)特征,同時(shí)也更容易被組合在一起。善用接口組合和小接口可以讓你的程序框架更加穩(wěn)定和靈活。