問題提要
之前寫代碼的時候遇到了一個問題:自己編寫了一個接口,然后又寫了一個結(jié)構(gòu)體實現(xiàn)這個接口,在通過函數(shù)調(diào)用接口方法時出現(xiàn)了問題。
代碼如下:
type Validator interface {
Valid() bool
}
type LoginInput struct {
Username string
Password string
}
func (input *LoginInput) Valid() bool {
// 一些檢驗邏輯
// 返回校驗結(jié)果
}
func Handle(v Validator) {
res := v.Valid()
// 根據(jù)校驗結(jié)果做一些邏輯處理
}
func main() {
// 對具體過程做了提煉昧辽,最終邏輯一致
input := LoginInput{Username: "XXX", Password: "YYY"}
Handle(input)
}
在main
中調(diào)用Handle()
時傳參失敗酵颁,Goland
提示消息如下:Cannot use 'input' (type LoginInput) as type ValidatorType does not implement 'Validator' as 'Valid' method has a pointer receiver
解決方法其實很簡單嫉你,就是調(diào)用Handle()
時不要傳值,要傳指針躏惋。把調(diào)用改成這樣就行:Handle(&input)
但這是為什么呢幽污?回去翻了翻書,發(fā)現(xiàn)是因為方法集
簿姨。
什么是方法集
我們先來看看Golang官方對它的描述:
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 typeT
consists of all methods declared with receiver typeT
. The method set of the corresponding pointer type*T
is the set of all methods declared with receiver*T
orT
(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.
一個類型會有一個與它關(guān)聯(lián)的方法集距误。interface類型的方法集就是接口本身。其他任意類型T
的方法集由接收者為T
類型的全部方法構(gòu)成。對應(yīng)的指針類型*T
的方法集是由接收者為T
或*T
的全部方法構(gòu)成的(也就是說准潭,它也包含了T的方法集)攘乒。更多的規(guī)則應(yīng)用在包含嵌入字段的結(jié)構(gòu)體上,就像struct types章節(jié)中描述的一樣惋鹅。任何其他類型都有一個空的方法集则酝。在方法集中,每個方法必須具有唯一的非空方法名闰集。
類型的方法集確定類型實現(xiàn)的接口以及可以使用該類型的接收器調(diào)用的方法沽讹。
總結(jié)一下官方文檔表述的意思,我們得到如下一張表:
變量類型 | 方法接收器類型 |
---|---|
T | (t T) |
*T | (t T) + (t *T) |
對于T
類型武鲁,它的方法集只包含接收者類型是T
的方法爽雄;而對于*T
類型,它的方法集則包含接收者為T
和*T
類型的方法沐鼠,也就是全部方法挚瘟。
只有一個類型的方法集完全涵蓋了接口的方法集后,這個類型才會被認為是接口的實現(xiàn)類型饲梭。
從這里可以看出來乘盖,我們最開始的代碼就是因為LoginInput
類型的方法集中沒有notify
方法,所以函數(shù)調(diào)用失敗了憔涉。
結(jié)構(gòu)體的方法調(diào)用與方法集之間的關(guān)系
其實到這里就會有個疑問:平時調(diào)用方法時订框,無論變量類型是值類型還是指針類型都能調(diào)用成功,也沒出過問題啊兜叨。
這里其實是Golang的一個語法糖:在使用選擇器(Selectors)調(diào)用方法時穿扳,編譯器會幫你做好取址或取值的操作的。
下面通過代碼說明一下這個關(guān)系:
type StructA struct {}
func (s StructA) ValueReceiver () {}
func (s *StructA) PointReceiver () {}
func main() {
value := StructA{}
point := &value
// 編譯器不做處理国旷,就是value.ValueReceiver()
value.ValueReceiver()
// 其實是(&value).ValueReceiver()的簡便寫法
value.PointReceiver()
// 其實是(*point).ValueReceiver()的簡便寫法
point.ValueReceiver()
// 編譯器不做處理矛物,就是point.ValueReceiver()
point.PointReceiver()
}