問(wèn)題提要
之前寫(xiě)代碼的時(shí)候遇到了一個(gè)問(wèn)題:自己編寫(xiě)了一個(gè)接口,然后又寫(xiě)了一個(gè)結(jié)構(gòu)體實(shí)現(xiàn)這個(gè)接口,在通過(guò)函數(shù)調(diào)用接口方法時(shí)出現(xiàn)了問(wèn)題。
代碼如下:
type Validator interface {
Valid() bool
}
type LoginInput struct {
Username string
Password string
}
func (input *LoginInput) Valid() bool {
// 一些檢驗(yàn)邏輯
// 返回校驗(yàn)結(jié)果
}
func Handle(v Validator) {
res := v.Valid()
// 根據(jù)校驗(yàn)結(jié)果做一些邏輯處理
}
func main() {
// 對(duì)具體過(guò)程做了提煉,最終邏輯一致
input := LoginInput{Username: "XXX", Password: "YYY"}
Handle(input)
}
在main中調(diào)用Handle()時(shí)傳參失敗,Goland提示消息如下:Cannot use 'input' (type LoginInput) as type ValidatorType does not implement 'Validator' as 'Valid' method has a pointer receiver
解決方法其實(shí)很簡(jiǎn)單,就是調(diào)用Handle()時(shí)不要傳值,要傳指針。把調(diào)用改成這樣就行:Handle(&input)
但這是為什么呢?回去翻了翻書(shū),發(fā)現(xiàn)是因?yàn)?code>方法集。
什么是方法集
我們先來(lái)看看Golang官方對(duì)它的描述:
https://golang.google.cn/ref/spec#Method_sets
A type may have a method set associated with it. The method set of an interface type is its interface. The method set of any other typeTconsists of all methods declared with receiver typeT. The method set of the corresponding pointer type*Tis the set of all methods declared with receiver*TorT(that is, it also contains the method set ofT). Further rules apply to structs containing embedded fields, as described in the section on struct types. Any other type has an empty method set. In a method set, each method must have a unique non-blank method name.
The method set of a type determines the interfaces that the type implements and the methods that can be called using a receiver of that type.
一個(gè)類型會(huì)有一個(gè)與它關(guān)聯(lián)的方法集。interface類型的方法集就是接口本身。其他任意類型T的方法集由接收者為T類型的全部方法構(gòu)成。對(duì)應(yīng)的指針類型*T的方法集是由接收者為T或*T的全部方法構(gòu)成的(也就是說(shuō),它也包含了T的方法集)。更多的規(guī)則應(yīng)用在包含嵌入字段的結(jié)構(gòu)體上,就像struct types章節(jié)中描述的一樣。任何其他類型都有一個(gè)空的方法集。在方法集中,每個(gè)方法必須具有唯一的非空方法名。
類型的方法集確定類型實(shí)現(xiàn)的接口以及可以使用該類型的接收器調(diào)用的方法。
總結(jié)一下官方文檔表述的意思,我們得到如下一張表:
| 變量類型 | 方法接收器類型 |
|---|---|
| T | (t T) |
| *T | (t T) + (t *T) |
對(duì)于T類型,它的方法集只包含接收者類型是T的方法;而對(duì)于*T類型,它的方法集則包含接收者為T和*T類型的方法,也就是全部方法。
只有一個(gè)類型的方法集完全涵蓋了接口的方法集后,這個(gè)類型才會(huì)被認(rèn)為是接口的實(shí)現(xiàn)類型。
從這里可以看出來(lái),我們最開(kāi)始的代碼就是因?yàn)?code>LoginInput類型的方法集中沒(méi)有notify方法,所以函數(shù)調(diào)用失敗了。
結(jié)構(gòu)體的方法調(diào)用與方法集之間的關(guān)系
其實(shí)到這里就會(huì)有個(gè)疑問(wèn):平時(shí)調(diào)用方法時(shí),無(wú)論變量類型是值類型還是指針類型都能調(diào)用成功,也沒(méi)出過(guò)問(wèn)題啊。
這里其實(shí)是Golang的一個(gè)語(yǔ)法糖:在使用選擇器(Selectors)調(diào)用方法時(shí),編譯器會(huì)幫你做好取址或取值的操作的。
下面通過(guò)代碼說(shuō)明一下這個(gè)關(guān)系:
type StructA struct {}
func (s StructA) ValueReceiver () {}
func (s *StructA) PointReceiver () {}
func main() {
value := StructA{}
point := &value
// 編譯器不做處理,就是value.ValueReceiver()
value.ValueReceiver()
// 其實(shí)是(&value).ValueReceiver()的簡(jiǎn)便寫(xiě)法
value.PointReceiver()
// 其實(shí)是(*point).ValueReceiver()的簡(jiǎn)便寫(xiě)法
point.ValueReceiver()
// 編譯器不做處理,就是point.ValueReceiver()
point.PointReceiver()
}