11 接口(Interfaces)與反射(reflection)

11.1 接口是什么

Go 語言不是一種“傳統(tǒng)”的面向對象編程語言:它里面沒有類和繼承的概念哆料。

但是 Go 語言里有非常靈活的接口概念,通過它可以實現(xiàn)很多面向對象的特性吗铐。接口提供了一種方式來說明對象的行為:如果誰能搞定這件事东亦,它就可以用在這兒。

接口定義了一組方法(方法集),但是這些方法不包含(實現(xiàn))代碼:它們沒有被實現(xiàn)(它們是抽象的)典阵。接口里也不能包含變量奋渔。

通過如下格式定義接口:

typeNamerinterface{Method1(param_list) return_typeMethod2(param_list) return_type? ? ...}

上面的Namer是一個接口類型

(按照約定壮啊,只包含一個方法的)接口的名字由方法名加[e]r后綴組成嫉鲸,例如Printer、Reader歹啼、Writer玄渗、Logger、Converter等等狸眼。還有一些不常用的方式(當后綴er不合適時)藤树,比如Recoverable,此時接口名以able結尾拓萌,或者以I開頭(像.NET或Java中那樣)岁钓。

Go 語言中的接口都很簡短,通常它們會包含 0 個微王、最多 3 個方法屡限。

不像大多數(shù)面向對象編程語言,在 Go 語言中接口可以有值炕倘,一個接口類型的變量或一個接口值:var ai Namer钧大,ai是一個多字(multiword)數(shù)據(jù)結構,它的值是nil罩旋。它本質上是一個指針拓型,雖然不完全是一回事。指向接口值的指針是非法的瘸恼,它們不僅一點用也沒有,還會導致代碼錯誤册养。

此處的方法指針表是通過運行時反射能力構建的东帅。

類型(比如結構體)實現(xiàn)接口方法集中的方法,每一個方法的實現(xiàn)說明了此方法是如何作用于該類型的:即實現(xiàn)接口球拦,同時方法集也構成了該類型的接口靠闭。實現(xiàn)了Namer接口類型的變量可以賦值給ai(接收者值),此時方法表中的指針會指向被實現(xiàn)的接口方法坎炼。當然如果另一個類型(也實現(xiàn)了該接口)的變量被賦值給ai愧膀,這二者(譯者注:指針和方法實現(xiàn))也會隨之改變。

類型不需要顯式聲明它實現(xiàn)了某個接口:接口被隱式地實現(xiàn)谣光。多個類型可以實現(xiàn)同一個接口檩淋。

實現(xiàn)某個接口的類型(除了實現(xiàn)接口方法外)可以有其他的方法

一個類型可以實現(xiàn)多個接口萄金。

接口類型可以包含一個實例的引用蟀悦, 該實例的類型實現(xiàn)了此接口(接口是動態(tài)類型)媚朦。

即使接口在類型之后才定義,二者處于不同的包中日戈,被單獨編譯:只要類型實現(xiàn)了接口中的方法询张,它就實現(xiàn)了此接口。

所有這些特性使得接口具有很大的靈活性浙炼。

第一個例子:

示例 11.1interfaces.go

packagemainimport"fmt"typeShaperinterface{Area()float32}typeSquarestruct{? ? sidefloat32}func(sq*Square)Area()float32{returnsq.side* sq.side}funcmain() {sq1:=new(Square)? ? sq1.side=5//var areaIntf Shaper//areaIntf = sq1//shorter,without separate declaration://areaIntf := Shaper(sq1)//or even:areaIntf:=sq1? ? fmt.Printf("The square has area:%f\n", areaIntf.Area())}

輸出:

The square has area: 25.000000

上面的程序定義了一個結構體Square和一個接口Shaper份氧,接口有一個方法Area()。

在main()方法中創(chuàng)建了一個Square的實例弯屈。在主程序外邊定義了一個接收者類型是Square方法的Area()蜗帜,用來計算正方形的面積:結構體Square實現(xiàn)了接口Shaper。

所以可以將一個Square類型的變量賦值給一個接口類型的變量:areaIntf = sq1季俩。

現(xiàn)在接口變量包含一個指向Square變量的引用钮糖,通過它可以調用Square上的方法Area()。當然也可以直接在Square的實例上調用此方法酌住,但是在接口實例上調用此方法更令人興奮店归,它使此方法更具有一般性。接口變量里包含了接收者實例的值和指向對應方法表的指針酪我。

這是多態(tài)的 Go 版本消痛,多態(tài)是面向對象編程中一個廣為人知的概念:根據(jù)當前的類型選擇正確的方法,或者說:同一種類型在不同的實例上似乎表現(xiàn)出不同的行為都哭。

如果Square沒有實現(xiàn)Area()方法秩伞,編譯器將會給出清晰的錯誤信息:

cannot use sq1 (type *Square) as type Shaper in assignment:

*Square does not implement Shaper (missing Area method)

如果Shaper有另外一個方法Perimeter(),但是Square沒有實現(xiàn)它欺矫,即使沒有人在Square實例上調用這個方法纱新,編譯器也會給出上面同樣的錯誤。

擴展一下上面的例子穆趴,類型Rectangle也實現(xiàn)了Shaper接口脸爱。接著創(chuàng)建一個Shaper類型的數(shù)組,迭代它的每一個元素并在上面調用Area()方法未妹,以此來展示多態(tài)行為:

示例 11.2interfaces_poly.go

packagemainimport"fmt"typeShaperinterface{Area()float32}typeSquarestruct{? ? sidefloat32}func(sq*Square)Area()float32{returnsq.side* sq.side}typeRectanglestruct{? ? length, widthfloat32}func(rRectangle)Area()float32{returnr.length* r.width}funcmain() {r:=Rectangle{5,3}//Area() of Rectangle needs a valueq:=&Square{5}//Area() of Square needs a pointer//shapes := []Shaper{Shaper(r), Shaper(q)}//or shortershapes:=[]Shaper{r, q}? ? fmt.Println("Looping through shapes for area ...")forn,_:=rangeshapes {? ? ? ? fmt.Println("Shape details:", shapes[n])? ? ? ? fmt.Println("Area of this shape is:", shapes[n].Area())? ? }}

輸出:

Looping through shapes for area ...

Shape details:? {5 3}

Area of this shape is:? 15

Shape details:? &{5}

Area of this shape is:? 25

在調用shapes[n].Area())這個時簿废,只知道shapes[n]是一個Shaper對象,最后它搖身一變成為了一個Square或Rectangle對象络它,并且表現(xiàn)出了相對應的行為族檬。

也許從現(xiàn)在開始你將看到通過接口如何產(chǎn)生更干凈更簡單更具有擴展性的代碼化戳。在 11.12.3 中將看到在開發(fā)中為類型添加新的接口是多么的容易单料。

下面是一個更具體的例子:有兩個類型stockPosition和car,它們都有一個getValue()方法,我們可以定義一個具有此方法的接口valuable看尼。接著定義一個使用valuable類型作為參數(shù)的函數(shù)showValue()递鹉,所有實現(xiàn)了valuable接口的類型都可以用這個函數(shù)。

示例 11.3valuable.go

packagemainimport"fmt"typestockPositionstruct{? ? tickerstringsharePricefloat32countfloat32}/*method to determine the value of a stock position*/func(sstockPosition)getValue()float32{returns.sharePrice* s.count}typecarstruct{makestringmodelstringpricefloat32}/*method to determine the value of a car*/func(ccar)getValue()float32{returnc.price}/*contract that defines different things that have value*/typevaluableinterface{getValue()float32}funcshowValue(assetvaluable) {? ? fmt.Printf("Value of the asset is%f\n", asset.getValue())}funcmain() {varovaluable = stockPosition{"GOOG",577.20,4}showValue(o)? ? o = car{"BMW","M3",66500}showValue(o)}

輸出:

Value of the asset is 2308.800049

Value of the asset is 66500.000000

一個標準庫的例子

io包里有一個接口類型Reader:

typeReaderinterface{Read(p []byte) (nint, errerror)}

定義變量r:var r io.Reader

那么就可以寫如下的代碼:

varrio.Readerr = os.Stdin//see 12.1r = bufio.NewReader(r)? ? r =new(bytes.Buffer)? ? f,_:=os.Open("test.txt")? ? r = bufio.NewReader(f)

上面r右邊的類型都實現(xiàn)了Read()方法藏斩,并且有相同的方法簽名躏结,r的靜態(tài)類型是io.Reader。

備注

有的時候狰域,也會以一種稍微不同的方式來使用接口這個詞:從某個類型的角度來看媳拴,它的接口指的是:它的所有導出方法,只不過沒有顯式地為這些導出方法額外定一個接口而已兆览。

練習 11.1simple_interface.go:

定義一個接口Simpler屈溉,它有一個Get()方法和一個Set(),Get()返回一個整型值抬探,Set()有一個整型參數(shù)子巾。創(chuàng)建一個結構體類型Simple實現(xiàn)這個接口。

接著定一個函數(shù)小压,它有一個Simpler類型的參數(shù)线梗,調用參數(shù)的Get()和Set()方法。在main函數(shù)里調用這個函數(shù)怠益,看看它是否可以正確運行仪搔。

練習 11.2interfaces_poly2.go:

a) 擴展 interfaces_poly.go 中的例子,添加一個Circle類型

b) 使用一個抽象類型Shape(沒有字段) 實現(xiàn)同樣的功能蜻牢,它實現(xiàn)接口Shaper烤咧,然后在其他類型里內嵌此類型。擴展 10.6.5 中的例子來說明覆寫抢呆。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末煮嫌,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子抱虐,更是在濱河造成了極大的恐慌立膛,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件梯码,死亡現(xiàn)場離奇詭異,居然都是意外死亡好啰,警方通過查閱死者的電腦和手機轩娶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來框往,“玉大人鳄抒,你說我怎么就攤上這事。” “怎么了许溅?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵瓤鼻,是天一觀的道長。 經(jīng)常有香客問我贤重,道長茬祷,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任并蝗,我火速辦了婚禮祭犯,結果婚禮上,老公的妹妹穿的比我還像新娘滚停。我一直安慰自己沃粗,他們只是感情好,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布键畴。 她就那樣靜靜地躺著最盅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪起惕。 梳的紋絲不亂的頭發(fā)上涡贱,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機與錄音疤祭,去河邊找鬼盼产。 笑死,一個胖子當著我的面吹牛勺馆,可吹牛的內容都是我干的戏售。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼草穆,長吁一口氣:“原來是場噩夢啊……” “哼灌灾!你這毒婦竟也來了?” 一聲冷哼從身側響起悲柱,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤锋喜,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后豌鸡,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嘿般,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年涯冠,在試婚紗的時候發(fā)現(xiàn)自己被綠了炉奴。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡蛇更,死狀恐怖瞻赶,靈堂內的尸體忽然破棺而出赛糟,到底是詐尸還是另有隱情,我是刑警寧澤砸逊,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布璧南,位于F島的核電站,受9級特大地震影響师逸,放射性物質發(fā)生泄漏司倚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一字旭、第九天 我趴在偏房一處隱蔽的房頂上張望对湃。 院中可真熱鬧,春花似錦遗淳、人聲如沸拍柒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拆讯。三九已至,卻和暖如春养叛,著一層夾襖步出監(jiān)牢的瞬間种呐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工弃甥, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留爽室,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓淆攻,卻偏偏與公主長得像阔墩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子瓶珊,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內容

  • fmt格式化字符串 格式:%[旗標][寬度][.精度][arg索引]動詞旗標有以下幾種:+: 對于數(shù)值類型總是輸出...
    皮皮v閱讀 1,089評論 0 3
  • 出處---Go編程語言 歡迎來到 Go 編程語言指南啸箫。本指南涵蓋了該語言的大部分重要特性 Go 語言的交互式簡介,...
    Tuberose閱讀 18,403評論 1 46
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,527評論 25 707
  • 第一次知道反射的時候還是許多年前在學校里玩 C# 的時候伞芹。那時總是弄不清楚這個復雜的玩意能有什么實際用途……然后發(fā)...
    勿以浮沙筑高臺閱讀 1,124評論 0 9
  • 要實現(xiàn)遠大理想忘苛,成為一名偉大的政治家,軍事家唱较,詩人扎唾,政論家,首當其沖的要務是先要將自身塑造成一位思想家以及哲學家南缓。...
    中庸陽閱讀 270評論 0 0