接口使用疑問(wèn)
golang
中的接口可以輕松實(shí)現(xiàn)C++
中的多態(tài)宽菜,而且沒(méi)有繼承自同一父類(lèi)的限制,感覺(jué)方便很多竿报。但是在使用的時(shí)候铅乡,如果沒(méi)有理解,也可能會(huì)遇到"坑"烈菌。比如《Go語(yǔ)言實(shí)戰(zhàn)》中的一個(gè)例子:
package main
import "fmt"
type user struct {
name string
email string
}
type notifier interface {
notify()
}
func (u *user) notify() {
fmt.Printf("sending user email to %s<%s>\n",
u.name,
u.email)
}
func sendNotification(n notifier) {
n.notify()
}
func main() {
u := user{
name: "stormzhu",
email: "abc@qq.com",
}
sendNotification(u)
}
// compile error
// cannot use u (type user) as type notifier in argument to sendNotification:
// user does not implement notifier (notify method has pointer receiver)
報(bào)的錯(cuò)是u
沒(méi)有實(shí)現(xiàn)notifier
這個(gè)接口阵幸,實(shí)現(xiàn)了這個(gè)接口的是*user
類(lèi)型,而不是user
類(lèi)型僧界,u
是user
類(lèi)型侨嘀,所以不能賦值給notifier
這個(gè)接口。
既然如此捂襟,修改為sendNotification(&u)
就OK了咬腕。然而問(wèn)題是,如何理解到底是T
類(lèi)型還是*T
類(lèi)型實(shí)現(xiàn)了某個(gè)接口呢葬荷?
接口的定義
參考雨痕的《Go語(yǔ)言學(xué)習(xí)筆記》第七章涨共,go
語(yǔ)言中的接口定義如下:
type iface struct {
tab *itab // 類(lèi)型信息
data unsafe.Pointer //實(shí)際對(duì)象指針
}
type itab struct {
inter *interfacetype // 接口類(lèi)型
_type *_type // 實(shí)際對(duì)象類(lèi)型
fun [1]uintptr // 實(shí)際對(duì)象方法地址
}
雖然具體的細(xì)節(jié)操作不太懂,但是可以知道宠漩,對(duì)一個(gè)接口賦值的時(shí)候举反,會(huì)拷貝類(lèi)型信息和該類(lèi)型的方法集。這就類(lèi)似于C++
多態(tài)中的虛指針(vptr
)和虛函數(shù)表(vtable
)了扒吁。我理解的是火鼻,只要這個(gè)類(lèi)型的方法集中包括這個(gè)接口的所有方法,那么它就是實(shí)現(xiàn)了這個(gè)接口,才能夠賦值給這個(gè)接口魁索,那么問(wèn)題來(lái)了刺彩,一個(gè)類(lèi)型的方法集是什么呢瞬雹?
方法集
同樣參考雨痕《Go語(yǔ)言學(xué)習(xí)筆記》第6章6.3節(jié)仗阅,書(shū)中總結(jié)的很全面:
- 類(lèi)型
T
的方法集包含所有receiver T
方法便斥。 - 類(lèi)型
*T
的方法集包含所有receiver T + *T
方法。 - 匿名嵌入
S
鹏控,類(lèi)型T
的方法集包含所有receiver T + S
方法致扯。 - 匿名嵌入
*S
,類(lèi)型T
的方法集包含所有receiver T + S + *S
方法当辐。 - 匿名嵌入
S
或*S
抖僵,類(lèi)型*T
的方法集包含所有receiver T + *T + S + *S
方法。
雖然看起來(lái)比較復(fù)雜瀑构,但總結(jié)完就一話裆针,*T
類(lèi)型就是厲害,方法集包括T
和*T
的方法寺晌。
所以文章開(kāi)頭的例子中世吨,u
是user
類(lèi)型,方法集是空的呻征,不算是實(shí)現(xiàn)了notifier
接口耘婚。
當(dāng)在糾結(jié)應(yīng)該將T
類(lèi)型還是*T
類(lèi)型賦值給某個(gè)接口的時(shí)候,第一步就是看方法集陆赋,看一看該類(lèi)型到底有沒(méi)有實(shí)現(xiàn)這個(gè)接口沐祷。(所以T
和*T
不是一個(gè)類(lèi)型。攒岛。赖临。)
一些例子
go
語(yǔ)言的內(nèi)置庫(kù)中有定義了很多接口,如error
接口灾锯,
type error interface {
Error() string
}
內(nèi)置的errors
包實(shí)現(xiàn)了這個(gè)接口:
// Package errors implements functions to manipulate errors.
package errors
// New returns an error that formats as the given text.
func New(text string) error {
return &errorString{text}
}
// errorString is a trivial implementation of error.
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
可以看到New
方法返回值是error
接口兢榨,而只有*errorString
類(lèi)型實(shí)現(xiàn)了這個(gè)接口,所以New
方法返回的是&errorString{text}
而不是errorString{text}
顺饮。
總結(jié)
-
T
和*T
不是一個(gè)類(lèi)型吵聪,他們的方法集不同 - 類(lèi)型
*T
的方法集包含所有receiver T + *T
方法,類(lèi)型T
的方法集只包含所有receiver T
方法兼雄。