8. 方法抽象與接口

上一節(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 對象 Sweetsumflower 對象 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做簡單介紹尿招。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市阱驾,隨后出現(xiàn)的幾起案子就谜,更是在濱河造成了極大的恐慌,老刑警劉巖里覆,帶你破解...
    沈念sama閱讀 222,946評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件丧荐,死亡現(xiàn)場離奇詭異,居然都是意外死亡喧枷,警方通過查閱死者的電腦和手機(jī)虹统,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,336評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來割去,“玉大人窟却,你說我怎么就攤上這事昼丑∩肽妫” “怎么了?”我有些...
    開封第一講書人閱讀 169,716評論 0 364
  • 文/不壞的土叔 我叫張陵菩帝,是天一觀的道長咖城。 經(jīng)常有香客問我茬腿,道長,這世上最難降的妖魔是什么宜雀? 我笑而不...
    開封第一講書人閱讀 60,222評論 1 300
  • 正文 為了忘掉前任切平,我火速辦了婚禮,結(jié)果婚禮上辐董,老公的妹妹穿的比我還像新娘悴品。我一直安慰自己,他們只是感情好简烘,可當(dāng)我...
    茶點故事閱讀 69,223評論 6 398
  • 文/花漫 我一把揭開白布苔严。 她就那樣靜靜地躺著,像睡著了一般孤澎。 火紅的嫁衣襯著肌膚如雪届氢。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,807評論 1 314
  • 那天覆旭,我揣著相機(jī)與錄音退子,去河邊找鬼。 笑死型将,一個胖子當(dāng)著我的面吹牛寂祥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播七兜,決...
    沈念sama閱讀 41,235評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼壤靶,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了惊搏?” 一聲冷哼從身側(cè)響起贮乳,我...
    開封第一講書人閱讀 40,189評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎恬惯,沒想到半個月后向拆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,712評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡酪耳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,775評論 3 343
  • 正文 我和宋清朗相戀三年浓恳,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碗暗。...
    茶點故事閱讀 40,926評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡颈将,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出言疗,到底是詐尸還是另有隱情晴圾,我是刑警寧澤,帶...
    沈念sama閱讀 36,580評論 5 351
  • 正文 年R本政府宣布噪奄,位于F島的核電站死姚,受9級特大地震影響人乓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜都毒,卻給世界環(huán)境...
    茶點故事閱讀 42,259評論 3 336
  • 文/蒙蒙 一色罚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧账劲,春花似錦戳护、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,750評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蝠猬,卻和暖如春切蟋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背榆芦。 一陣腳步聲響...
    開封第一講書人閱讀 33,867評論 1 274
  • 我被黑心中介騙來泰國打工柄粹, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人匆绣。 一個月前我還...
    沈念sama閱讀 49,368評論 3 379
  • 正文 我出身青樓驻右,卻偏偏與公主長得像,于是被迫代替她去往敵國和親崎淳。 傳聞我的和親對象是個殘疾皇子堪夭,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,930評論 2 361