方法
認(rèn)識(shí) Go 方法
- Go 語(yǔ)言從設(shè)計(jì)伊始,就不支持經(jīng)典的面向?qū)ο笳Z(yǔ)法元素,比如類、對(duì)象、繼承,等等,但 Go 語(yǔ)言仍保留了名為“方法(method)”的語(yǔ)法元素。當(dāng)然,Go 語(yǔ)言中的方法和面向?qū)ο笾械姆椒ú⒉皇且粯拥?。Go 引入方法這一元素,并不是要支持面向?qū)ο缶幊谭妒?,而?Go 踐行組合設(shè)計(jì)哲學(xué)的一種實(shí)現(xiàn)層面的需要。
- Go 的方法也是以 func 關(guān)鍵字修飾的,并且和函數(shù)一樣,也包含方法名(對(duì)應(yīng)函數(shù)名)、參數(shù)列表、返回值列表與方法體(對(duì)應(yīng)函數(shù)體)。
- 方法中的這幾個(gè)部分和函數(shù)聲明中對(duì)應(yīng)的部分,在形式與語(yǔ)義方面都是一致的,比如:方法名字首字母大小寫(xiě)決定該方法是否是導(dǎo)出方法;方法參數(shù)列表支持變長(zhǎng)參數(shù);方法的返回值列表也支持具名返回值等。
- 和由五個(gè)部分組成的函數(shù)聲明不同,Go 方法的聲明有六個(gè)組成部分,多的一個(gè)就是圖中的 receiver 部分。在 receiver 部分聲明的參數(shù),Go 稱之為 receiver 參數(shù),這個(gè) receiver 參數(shù)也是方法與類型之間的紐帶,也是方法與函數(shù)的最大不同。
- Go 中的方法必須是歸屬于一個(gè)類型的,而 receiver 參數(shù)的類型就是這個(gè)方法歸屬的類型,或者說(shuō)這個(gè)方法就是這個(gè)類型的一個(gè)方法。
- 這里的 receiver 參數(shù) srv 的類型為 *Server,那么我們可以說(shuō),這個(gè)方法就是 *Server 類型的方法。
- 每個(gè)方法只能有一個(gè) receiver 參數(shù),Go 不支持在方法的 receiver 部分放置包含多個(gè) receiver 參數(shù)的參數(shù)列表,或者變長(zhǎng) receiver 參數(shù)。
- 方法接收器(receiver)參數(shù)、函數(shù) / 方法參數(shù),以及返回值變量對(duì)應(yīng)的作用域范圍,都是函數(shù) / 方法體對(duì)應(yīng)的顯式代碼塊。
- receiver 部分的參數(shù)名不能與方法參數(shù)列表中的形參名,以及具名返回值中的變量名存在沖突,必須在這個(gè)方法的作用域中具有唯一性。
- 除了 receiver 參數(shù)名字要保證唯一外,Go 語(yǔ)言對(duì) receiver 參數(shù)的基類型也有約束,那就是 receiver 參數(shù)的基類型本身不能為指針類型或接口類型。
-
Go 要求,方法聲明要與 receiver 參數(shù)的基類型聲明放在同一個(gè)包內(nèi)。
- 我們不能為原生類型(諸如 int、float64、map 等)添加方法。
- 不能跨越 Go 包為其他包的類型聲明新方法。
- Go 語(yǔ)言中的方法的本質(zhì)就是,一個(gè)以方法的 receiver 參數(shù)作為第一個(gè)參數(shù)的普通函數(shù)。
方法集合與如何選擇 receiver 類型
receiver 參數(shù)類型對(duì) Go 方法的影響
- 我們直接來(lái)看下面例子中的兩個(gè) Go 方法,以及它們等價(jià)轉(zhuǎn)換后的函數(shù):
func (t T) M1() <=> F1(t T) func (t *T) M2() <=> F2(t *T)
- 首先,當(dāng) receiver 參數(shù)的類型為 T 時(shí):
- 當(dāng)我們選擇以 T 作為 receiver 參數(shù)類型時(shí),M1 方法等價(jià)轉(zhuǎn)換為F1(t T)。
- Go 函數(shù)的參數(shù)采用的是值拷貝傳遞,也就是說(shuō),F(xiàn)1 函數(shù)體中的 t 是 T 類型實(shí)例的一個(gè)副本。
- 這樣,我們?cè)?F1 函數(shù)的實(shí)現(xiàn)中對(duì)參數(shù) t 做任何修改,都只會(huì)影響副本,而不會(huì)影響到原 T 類型實(shí)例。
- 當(dāng)我們的方法 M1 采用類型為 T 的 receiver 參數(shù)時(shí),代表 T 類型實(shí)例的 receiver 參數(shù)以值傳遞方式傳遞到 M1 方法體中的,實(shí)際上是 T 類型實(shí)例的副本,M1 方法體中對(duì)副本的任何修改操作,都不會(huì)影響到原 T 類型實(shí)例。
- 第二,當(dāng) receiver 參數(shù)的類型為 *T 時(shí):
- 當(dāng)我們選擇以 *T 作為 receiver 參數(shù)類型時(shí),M2 方法等價(jià)轉(zhuǎn)換為F2(t *T)。
- 我們傳遞給 F2 函數(shù)的 t 是 T 類型實(shí)例的地址,這樣 F2 函數(shù)體中對(duì)參數(shù) t 做的任何修改,都會(huì)反映到原 T 類型實(shí)例上。
- 當(dāng)我們的方法 M2 采用類型為 *T 的 receiver 參數(shù)時(shí),代表 *T 類型實(shí)例的 receiver 參數(shù)以值傳遞方式傳遞到 M2 方法體中的,實(shí)際上是 T 類型實(shí)例的地址,M2 方法體通過(guò)該地址可以對(duì)原 T 類型實(shí)例進(jìn)行任何修改操作。
選擇 receiver 參數(shù)類型的原則
- 第一個(gè)原則
- *如果 Go 方法要把對(duì) receiver 參數(shù)代表的類型實(shí)例的修改,反映到原類型實(shí)例上,那么我們應(yīng)該選擇 T 作為 receiver 參數(shù)的類型。
- 無(wú)論是 T 類型實(shí)例,還是 *T 類型實(shí)例,都既可以調(diào)用 receiver 為 T 類型的方法,也可以調(diào)用 receiver 為 *T 類型的方法。
- 第二個(gè)原則
- 如果我們不需要在方法中對(duì)類型實(shí)例進(jìn)行修改呢?這個(gè)時(shí)候我們是為 receiver 參數(shù)選擇 T 類型還是 *T 類型呢?
- 一般情況下,我們通常會(huì)為 receiver 參數(shù)選擇 T 類型,因?yàn)檫@樣可以縮窄外部修改類型實(shí)例內(nèi)部狀態(tài)的“接觸面”,也就是盡量少暴露可以修改類型內(nèi)部狀態(tài)的方法。
- *考慮到 Go 方法調(diào)用時(shí),receiver 參數(shù)是以值拷貝的形式傳入方法中的。那么,如果 receiver 參數(shù)類型的 size 較大,以值拷貝形式傳入就會(huì)導(dǎo)致較大的性能開(kāi)銷(xiāo),這時(shí)我們選擇 T 作為 receiver 類型可能更好些。
- 第三個(gè)原則
- 方法集合是用來(lái)判斷一個(gè)類型是否實(shí)現(xiàn)了某接口類型的唯一手段,可以說(shuō),“方法集合決定了接口實(shí)現(xiàn)”。
- Go 中任何一個(gè)類型都有屬于自己的方法集合,或者說(shuō)方法集合是 Go 類型的一個(gè)“屬性”。但不是所有類型都有自己的方法,比如 int 類型就沒(méi)有。所以,對(duì)于沒(méi)有定義方法的 Go 類型,我們稱其擁有空方法集合。
- 接口類型相對(duì)特殊,它只會(huì)列出代表接口的方法列表,不會(huì)具體定義某個(gè)方法,它的方法集合就是它的方法列表中的所有方法,我們可以一目了然地看到。
- Go 語(yǔ)言規(guī)定,*T 類型的方法集合包含所有以 *T 為 receiver 參數(shù)類型的方法,以及所有以 T 為 receiver 參數(shù)類型的方法。
- 如果某類型 T 的方法集合與某接口類型的方法集合相同,或者類型 T 的方法集合是接口類型 I 方法集合的超集,那么我們就說(shuō)這個(gè)類型 T 實(shí)現(xiàn)了接口 I。
- 如果 T 類型需要實(shí)現(xiàn)某個(gè)接口,那我們就要使用 T 作為 receiver 參數(shù)的類型,來(lái)滿足接口類型方法集合中的所有方法。
- 如果 T 不需要實(shí)現(xiàn)某一接口,但 *T 需要實(shí)現(xiàn)該接口,那么根據(jù)方法集合概念,*T 的方法集合是包含 T 的方法集合的,這樣我們?cè)诖_定 Go 方法的 receiver 的類型時(shí),參考原則一和原則二就可以了。
- 方法集合是用來(lái)判斷一個(gè)類型是否實(shí)現(xiàn)了某接口類型的唯一手段,可以說(shuō),“方法集合決定了接口實(shí)現(xiàn)”。
如何用類型嵌入模擬實(shí)現(xiàn)“繼承”
什么是類型嵌入
- 類型嵌入指的就是在一個(gè)類型的定義中嵌入了其他類型。Go 語(yǔ)言支持兩種類型嵌入,分別是接口類型的類型嵌入和結(jié)構(gòu)體類型的類型嵌入。
- 接口類型的類型嵌入
- 接口類型聲明了由一個(gè)方法集合代表的接口,比如下面接口類型 E:
type E interface { M1() M2() }
- 這個(gè)接口類型 E 的方法集合,包含兩個(gè)方法,分別是 M1 和 M2,它們組成了 E 這個(gè)接口類型所代表的接口。
- 如果某個(gè)類型實(shí)現(xiàn)了方法 M1 和 M2,我們就說(shuō)這個(gè)類型實(shí)現(xiàn)了 E 所代表的接口。
- 此時(shí),我們?cè)俣x另外一個(gè)接口類型 I,它的方法集合中包含了三個(gè)方法 M1、M2 和 M3,如下面代碼:
type I interface { M1() M2() M3() }
- 接口類型 I 方法集合中的 M1 和 M2,與接口類型 E 的方法集合中的方法完全相同。在這種情況下,我們可以用接口類型 E 替代上面接口類型 I 定義中 M1 和 M2:
type I interface { EM 3() }
- 在一個(gè)接口類型(I)定義中,嵌入另外一個(gè)接口類型(E)的方式,就是接口類型的類型嵌入。
- 接口類型嵌入的語(yǔ)義就是新接口類型(如接口類型 I)將嵌入的接口類型(如接口類型 E)的方法集合,并入到自己的方法集合中。
- 接口類型聲明了由一個(gè)方法集合代表的接口,比如下面接口類型 E:
- 結(jié)構(gòu)體類型的類型嵌入
- Go 結(jié)構(gòu)體類型定義有另外一種形式,那就是帶有嵌入字段(Embedded Field)的結(jié)構(gòu)體定義。
type T1 int type t2 struct{ n int m int } type I interface { M1() } type S1 struct { T1 *t2 Ia int b string }
- 這種以某個(gè)類型名、類型的指針類型名或接口類型名,直接作為結(jié)構(gòu)體字段的方式就叫做結(jié)構(gòu)體的類型嵌入,這些字段也被叫做嵌入字段(Embedded Field)。
- 如果嵌入類型的名字是首字母大寫(xiě)的,那么也就說(shuō)明這個(gè)嵌入字段是可導(dǎo)出的。
- Go 結(jié)構(gòu)體類型定義有另外一種形式,那就是帶有嵌入字段(Embedded Field)的結(jié)構(gòu)體定義。
類型嵌入與方法集合
- 嵌入類型的方法集合并入到新接口類型的方法集合中,并且,接口類型只能嵌入接口類型。而結(jié)構(gòu)體類型對(duì)嵌入類型的要求就比較寬泛了,可以是任意自定義類型或接口類型。
- 結(jié)構(gòu)體類型的方法集合,包含嵌入的接口類型的方法集合。
-
嵌入了其他類型的結(jié)構(gòu)體類型本身是一個(gè)代理,在調(diào)用其實(shí)例所代理的方法時(shí),Go 會(huì)首先查看結(jié)構(gòu)體自身是否實(shí)現(xiàn)了該方法。
- 如果實(shí)現(xiàn)了,Go 就會(huì)優(yōu)先使用結(jié)構(gòu)體自己實(shí)現(xiàn)的方法。
- 如果沒(méi)有實(shí)現(xiàn),那么 Go 就會(huì)查找結(jié)構(gòu)體中的嵌入字段的方法集合中,是否包含了這個(gè)方法。
- 如果多個(gè)嵌入字段的方法集合中都包含這個(gè)方法,那么我們就說(shuō)方法集合存在交集。
- 這個(gè)時(shí)候,Go 編譯器就會(huì)因無(wú)法確定究竟使用哪個(gè)方法而報(bào)錯(cuò)。
- 結(jié)構(gòu)體類型嵌入接口類型在日常編碼中有一個(gè)妙用,就是可以簡(jiǎn)化單元測(cè)試的編寫(xiě)。
- 由于嵌入某接口類型的結(jié)構(gòu)體類型的方法集合包含了這個(gè)接口類型的方法集合,這就意味著,這個(gè)結(jié)構(gòu)體類型也是它嵌入的接口類型的一個(gè)實(shí)現(xiàn)。
- 即便結(jié)構(gòu)體類型自身并沒(méi)有實(shí)現(xiàn)這個(gè)接口類型的任意一個(gè)方法,也沒(méi)有關(guān)系。
- 在結(jié)構(gòu)體類型中嵌入結(jié)構(gòu)體類型,為 Gopher 們提供了一種“實(shí)現(xiàn)繼承”的手段,外部的結(jié)構(gòu)體類型 T 可以“繼承”嵌入的結(jié)構(gòu)體類型的所有方法的實(shí)現(xiàn)。并且,無(wú)論是 T 類型的變量實(shí)例還是 *T 類型變量實(shí)例,都可以調(diào)用所有“繼承”的方法。
- Go 語(yǔ)言中,凡通過(guò)類型聲明語(yǔ)法聲明的類型都被稱為 defined 類型。
- 對(duì)于那些基于接口類型創(chuàng)建的 defined 的接口類型,它們的方法集合與原接口類型的方法集合是一致的。
- 對(duì)于基于非接口類型的 defined 類型創(chuàng)建的非接口類型,基于自定義非接口類型的 defined 類型的方法集合為空的事實(shí),也決定了即便原類型實(shí)現(xiàn)了某些接口,基于其創(chuàng)建的 defined 類型也沒(méi)有“繼承”這一隱式關(guān)聯(lián)。也就是說(shuō),新 defined 類型要想實(shí)現(xiàn)那些接口,仍然需要重新實(shí)現(xiàn)接口的所有方法。
- 無(wú)論原類型是接口類型還是非接口類型,類型別名都與原類型擁有完全相同的方法集合。
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-683236.html
文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-683236.html
到了這里,關(guān)于《Go 語(yǔ)言第一課》課程學(xué)習(xí)筆記(十三)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!