上一節(jié)中談到了面向?qū)ο箝菪。⑶叶x了一堆方法,從對象的角度定義了其操作的方法妆够。然而识啦,你是誰,我并不關(guān)心神妹,我只關(guān)心你能為我干什么颓哮,是所有不同對象接觸的一個界面。在Go中鸵荠,接口就是這樣一種抽象類型冕茅,它定義了一個對象的行為,即定義了你應(yīng)該做什么蛹找,但具體怎么做姨伤,接口并不管。如果說封裝是保護(hù)了代碼實現(xiàn)的內(nèi)部庸疾,那么接口則是規(guī)范了交互約定乍楚,保護(hù)了外部代碼,兩者是相輔相成的届慈。定義接口對于團(tuán)隊協(xié)作徒溪,保護(hù)外部代碼,維持歷史軟件版本非常重要金顿。具體來說臊泌,接口是一個或幾個方法簽名的集合,如果一個類型定義了接口中所有方法揍拆,就實現(xiàn)了該接口渠概。Unix/Linux系統(tǒng)中流傳著一句話叫 "Everything is a file",即一切對象都是文件(更為確切的應(yīng)當(dāng)是一切對象都是文件描述符)設(shè)備是文件自身是文件嫂拴、設(shè)備是文件播揪、網(wǎng)絡(luò)是文件,進(jìn)程都可以是一個文件,可以文件是一個抽象筒狠,一切支持讀寫操作接口剪芍,事實上操作一個普通文件和操作一個設(shè)備,其底層差異是非常巨大的窟蓝,但面對對象與接口設(shè)計讓他們都成為了文件罪裹。
接口的定義與聲明
// 定義接口
type InterfaceNameA interface {
Method1()
Method2()
}
type InterfaceNameB interface{
Method3()
Method4()
}
type InterfaceNameC interface{
InterfaceNameA
InterfaceNameB
Method5()
}
// 聲明
var IN InterfaceNameA
// 調(diào)用
IN = DataType
IN.MethodX()
接口的定義不像結(jié)構(gòu)體饱普,其對順序沒有要求,或者說不區(qū)分順序状共。在接口中可以直接寫入方法簽名或使用另一個接口套耕。要實現(xiàn)一個接口,就必須實現(xiàn)接口中的所有方法峡继。一個數(shù)據(jù)類型可以實現(xiàn)多個接口類型冯袍。
接口的聲明需要通過 var
關(guān)鍵字實現(xiàn),無法使用類型推導(dǎo)碾牌。接口類型的零值是 nil
康愤,如果聲明接口卻沒有賦值或沒有實現(xiàn),調(diào)用就會出錯舶吗。
下面看一個具體的例子征冷。假設(shè)名為"生長"的接口,里面包含"開花"誓琼、"結(jié)果"兩個方法,用于演示植物生長過程检激。
package main
import (
"fmt"
"time"
)
// GrowUp interface has methods bloom, fructify
type GrowUp interface {
bloom() string
fructify() string
}
type sumflower string
type apple string
func (app apple) bloom() string {
return fmt.Sprintf("Apple Tree %s Bloom at %s", string(app), time.Now().Format("2006-01-02 15:04:05"))
}
func (app apple) fructify() string {
return fmt.Sprintf("Apple Tree %s Fructified at %s", string(app), time.Now().Format("2006-01-02 15:04:05"))
}
func (sf sumflower) bloom() string {
return fmt.Sprintf("Sumflower %s Bloom at %s", string(sf), time.Now().Format("2006-01-02 15:04:05"))
}
func (sf sumflower) fructify() string {
return fmt.Sprintf("Sumflower %s Fructified at %s", string(sf), time.Now().Format("2006-01-02 15:04:05"))
}
func main() {
Littlesmile := sumflower("LittleSmile")
Sweet := apple("Sweet")
var gup GrowUp
gup = Littlesmile
fmt.Println("This is", Littlesmile)
fmt.Println(gup.bloom())
time.Sleep(2 * time.Second)
fmt.Println(gup.fructify())
gup = Sweet
fmt.Println("This is", Sweet)
fmt.Println(gup.bloom())
time.Sleep(3 * time.Second)
fmt.Println(gup.fructify())
}
/* ------------Result----------
This is LittleSmile
Sumflower LittleSmile Bloom at 2018-11-20 00:44:33
Sumflower LittleSmile Fructified at 2018-11-20 00:44:35
This is Sweet
Apple Tree Sweet Bloom at 2018-11-20 00:44:35
Apple Tree Sweet Fructified at 2018-11-20 00:44:38
*/
//跟其他語言不同,Go 實現(xiàn)一個接口不需要顯式說明腹侣,只要實現(xiàn)了叔收,就可以使用。
在這個例子中傲隶,聲明了兩種植物向日葵(sumflower)和蘋果饺律,按照接口要求,都定義了“開花”和“結(jié)果”的方法跺株,與前一節(jié)面向?qū)ο缶幊虝r的main函數(shù)不同复濒,我們沒有單純的使用創(chuàng)建對象,調(diào)用對象方法帖鸦,而是創(chuàng)建了對象芝薇,聲明了GroupUp接口類型變量 gup
胚嘲,然后神奇的將 apple
對象 Sweet
與 sumflower
對象 Littlesmile
賦值給了 gup
作儿,然后還成功的調(diào)用了他們的方法展現(xiàn)了生長的過程。這就是接口馋劈,它只關(guān)心方法攻锰,不關(guān)心對象,這對于進(jìn)一步處理更多類型的“生長”留下了良好的基礎(chǔ)妓雾,因為你不必再關(guān)心那些植物有哪些可用的方法了娶吞。如果有一個植物沒有實現(xiàn) GrowUp
接口,然后賦值給 gup
會發(fā)生什么械姻,會在編譯時生成panic導(dǎo)致失敗妒蛇。對于 var gup GrowUp
如果沒有經(jīng)過 gup =
的賦值,那么 gup
就會是一個 nil
,這也是接口唯一可以比較的對象 gup == nil
绣夺,如果判定成功吏奸,那么對于后續(xù)接口的使用就應(yīng)該立即避免,否則會引發(fā) panic
陶耍。
接口是一個interface
類型變量奋蔚,那么它就和其他所有類型一致,所有類型值可以使用的習(xí)慣烈钞,在接口處也全部成立泊碑。例如,切片毯欣、循環(huán)馒过、指針等等。
sumflowerA := sumflower("sumflowerA")
sumflowerB := sumflower("sumflowerB")
appleA := apple("appleA")
appleB := apple("appleB")
gups := []GrowUp{sumflowerA, sumflowerB, appleA, appleB}
for _, v := range gups {
fmt.Println(v.bloom())
}
/* ----result----------
Sumflower sumflowerA Bloom at 2018-11-20 10:03:34
Sumflower sumflowerB Bloom at 2018-11-20 10:03:34
Apple Tree appleA Bloom at 2018-11-20 10:03:34
Apple Tree appleB Bloom at 2018-11-20 10:03:34
如果一個接口沒有約定方法 type i interface{}
仪媒,將其稱之為空接口骄蝇,所有類型都實現(xiàn)了空接口扛门。空接口有什么用呢?如上那個例子览濒,空接口可以作為任何類型的接收器,用于承載任何數(shù)據(jù)拟糕。之前都沒有做任何接口數(shù)據(jù)的介紹屡限,只關(guān)注方法,接口確實是這樣压昼,但不代表接口沒有值求冷。接口在Go內(nèi)部可視為(type, value)
表達(dá),type 為底層一種數(shù)據(jù)類型窍霞,value就是value匠题,正是由于這種特性,接口才能承載任何對象但金【律剑看下面例子,可能會理解的更為直觀一點冷溃。
for _, v := range gups {
fmt.Printf("%T %+v",v,v)
}
/* ---result-----
main.sumflower sumflowerA
main.sumflower sumflowerB
main.apple appleA
main.apple appleB
*/
假如知道底層類型钱磅,那是不是可以獲取是值呢,答案是肯定的似枕,這需要用到類型斷言盖淡,怎么叫斷言,就是對數(shù)據(jù)強(qiáng)制轉(zhuǎn)換凿歼。typevalue, ok := i.(type)
褪迟,例如延續(xù)上面的例子
// 將循環(huán)的語句修改為
fmt.Printf("%T %+v\n", v.(sumflower), v.(sumflower))
/* ----- result ---------
main.sumflower sumflowerA
main.sumflower sumflowerB
panic: interface conversion: main.GrowUp is main.apple, not main.sumflower
斷言結(jié)果無非兩種:如果是 sumflower 類型冗恨,那么就斷言成功,獲取sumflower的值味赃,如果是 apple 類型派近,那么斷言失敗,導(dǎo)致運行panic異常洁桌。如果完整使用斷言 typevalue, ok := i.(type)
這時斷言失敗渴丸,就不會引發(fā) panic。除非知道自己干什么另凌,否則不要輕易斷言谱轨。
sfv, ok := v.(sumflower)
if ok {
fmt.Printf("%T %+v\n", sfv, sfv)
}
/* --------result------------
main.sumflower sumflowerA
main.sumflower sumflowerB
*/
還記得fmt.Println()
,它可以打印任何類型值吠谢,看一下 fmt.Println()
簽名土童,func Println(a ...interface{}) (n int, err error)
就是一個空接口,如果現(xiàn)在去實現(xiàn)這么一個功能工坊,就可以這樣做
switch i.(type){
case int: fmt.Printf("%d\n",i.(int))
case float64: fmt.Printf("%.2f\n",i.(float64))
case string: fmt.Printf("%s\n",i.(string))
default: fmt.Printf("Unknown\n")
}
GO 庫中存在很多接口献汗,io.Reader,io.Writer王污,fmt.Stringer罢吃,sort.Interface,http.Handler
昭齐,將在X.5做簡單介紹尿招。