W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
原文鏈接:https://gopl-zh.github.io/ch7/ch7-03.html
一個(gè)類(lèi)型如果擁有一個(gè)接口需要的所有方法,那么這個(gè)類(lèi)型就實(shí)現(xiàn)了這個(gè)接口。例如,*os.File
類(lèi)型實(shí)現(xiàn)了io.Reader,Writer,Closer,和ReadWriter接口。*bytes.Buffer
實(shí)現(xiàn)了Reader,Writer,和ReadWriter這些接口,但是它沒(méi)有實(shí)現(xiàn)Closer接口因?yàn)樗痪哂蠧lose方法。Go的程序員經(jīng)常會(huì)簡(jiǎn)要的把一個(gè)具體的類(lèi)型描述成一個(gè)特定的接口類(lèi)型。舉個(gè)例子,*bytes.Buffer
是io.Writer;*os.Files
是io.ReadWriter。
接口指定的規(guī)則非常簡(jiǎn)單:表達(dá)一個(gè)類(lèi)型屬于某個(gè)接口只要這個(gè)類(lèi)型實(shí)現(xiàn)這個(gè)接口。所以:
var w io.Writer
w = os.Stdout // OK: *os.File has Write method
w = new(bytes.Buffer) // OK: *bytes.Buffer has Write method
w = time.Second // compile error: time.Duration lacks Write method
var rwc io.ReadWriteCloser
rwc = os.Stdout // OK: *os.File has Read, Write, Close methods
rwc = new(bytes.Buffer) // compile error: *bytes.Buffer lacks Close method
這個(gè)規(guī)則甚至適用于等式右邊本身也是一個(gè)接口類(lèi)型
w = rwc // OK: io.ReadWriteCloser has Write method
rwc = w // compile error: io.Writer lacks Close method
因?yàn)镽eadWriter和ReadWriteCloser包含有Writer的方法,所以任何實(shí)現(xiàn)了ReadWriter和ReadWriteCloser的類(lèi)型必定也實(shí)現(xiàn)了Writer接口
在進(jìn)一步學(xué)習(xí)前,必須先解釋一個(gè)類(lèi)型持有一個(gè)方法的表示當(dāng)中的細(xì)節(jié)?;叵朐?.2章中,對(duì)于每一個(gè)命名過(guò)的具體類(lèi)型T;它的一些方法的接收者是類(lèi)型T本身然而另一些則是一個(gè)*T
的指針。還記得在T類(lèi)型的參數(shù)上調(diào)用一個(gè)*T
的方法是合法的,只要這個(gè)參數(shù)是一個(gè)變量;編譯器隱式的獲取了它的地址。但這僅僅是一個(gè)語(yǔ)法糖:T類(lèi)型的值不擁有所有*T
指針的方法,這樣它就可能只實(shí)現(xiàn)了更少的接口。
舉個(gè)例子可能會(huì)更清晰一點(diǎn)。在第6.5章中,IntSet類(lèi)型的String方法的接收者是一個(gè)指針類(lèi)型,所以我們不能在一個(gè)不能尋址的IntSet值上調(diào)用這個(gè)方法:
type IntSet struct { /* ... */ }
func (*IntSet) String() string
var _ = IntSet{}.String() // compile error: String requires *IntSet receiver
但是我們可以在一個(gè)IntSet變量上調(diào)用這個(gè)方法:
var s IntSet
var _ = s.String() // OK: s is a variable and &s has a String method
然而,由于只有*IntSet
類(lèi)型有String方法,所以也只有*IntSet
類(lèi)型實(shí)現(xiàn)了fmt.Stringer接口:
var _ fmt.Stringer = &s // OK
var _ fmt.Stringer = s // compile error: IntSet lacks String method
12.8章包含了一個(gè)打印出任意值的所有方法的程序,然后可以使用godoc -analysis=type tool(§10.7.4)展示每個(gè)類(lèi)型的方法和具體類(lèi)型和接口之間的關(guān)系
就像信封封裝和隱藏起信件來(lái)一樣,接口類(lèi)型封裝和隱藏具體類(lèi)型和它的值。即使具體類(lèi)型有其它的方法,也只有接口類(lèi)型暴露出來(lái)的方法會(huì)被調(diào)用到:
os.Stdout.Write([]byte("hello")) // OK: *os.File has Write method
os.Stdout.Close() // OK: *os.File has Close method
var w io.Writer
w = os.Stdout
w.Write([]byte("hello")) // OK: io.Writer has Write method
w.Close() // compile error: io.Writer lacks Close method
一個(gè)有更多方法的接口類(lèi)型,比如io.ReadWriter,和少一些方法的接口類(lèi)型例如io.Reader,進(jìn)行對(duì)比;更多方法的接口類(lèi)型會(huì)告訴我們更多關(guān)于它的值持有的信息,并且對(duì)實(shí)現(xiàn)它的類(lèi)型要求更加嚴(yán)格。那么關(guān)于interface{}類(lèi)型,它沒(méi)有任何方法,請(qǐng)講出哪些具體的類(lèi)型實(shí)現(xiàn)了它?
這看上去好像沒(méi)有用,但實(shí)際上interface{}被稱為空接口類(lèi)型是不可或缺的。因?yàn)榭战涌陬?lèi)型對(duì)實(shí)現(xiàn)它的類(lèi)型沒(méi)有要求,所以我們可以將任意一個(gè)值賦給空接口類(lèi)型。
var any interface{}
any = true
any = 12.34
any = "hello"
any = map[string]int{"one": 1}
any = new(bytes.Buffer)
盡管不是很明顯,從本書(shū)最早的例子中我們就已經(jīng)在使用空接口類(lèi)型。它允許像fmt.Println或者5.7章中的errorf函數(shù)接受任何類(lèi)型的參數(shù)。
對(duì)于創(chuàng)建的一個(gè)interface{}值持有一個(gè)boolean,float,string,map,pointer,或者任意其它的類(lèi)型;我們當(dāng)然不能直接對(duì)它持有的值做操作,因?yàn)閕nterface{}沒(méi)有任何方法。我們會(huì)在7.10章中學(xué)到一種用類(lèi)型斷言來(lái)獲取interface{}中值的方法。
因?yàn)榻涌谂c實(shí)現(xiàn)只依賴于判斷兩個(gè)類(lèi)型的方法,所以沒(méi)有必要定義一個(gè)具體類(lèi)型和它實(shí)現(xiàn)的接口之間的關(guān)系。也就是說(shuō),有意地在文檔里說(shuō)明或者程序上斷言這種關(guān)系偶爾是有用的,但程序上不強(qiáng)制這么做。下面的定義在編譯期斷言一個(gè)*bytes.Buffer
的值實(shí)現(xiàn)了io.Writer接口類(lèi)型:
// *bytes.Buffer must satisfy io.Writer
var w io.Writer = new(bytes.Buffer)
因?yàn)槿我?code>*bytes.Buffer的值,甚至包括nil通過(guò)(*bytes.Buffer)(nil)
進(jìn)行顯示的轉(zhuǎn)換都實(shí)現(xiàn)了這個(gè)接口,所以我們不必分配一個(gè)新的變量。并且因?yàn)槲覀兘^不會(huì)引用變量w,我們可以使用空標(biāo)識(shí)符來(lái)進(jìn)行代替??偟目矗@些變化可以讓我們得到一個(gè)更樸素的版本:
// *bytes.Buffer must satisfy io.Writer
var _ io.Writer = (*bytes.Buffer)(nil)
非空的接口類(lèi)型比如io.Writer經(jīng)常被指針類(lèi)型實(shí)現(xiàn),尤其當(dāng)一個(gè)或多個(gè)接口方法像Write方法那樣隱式的給接收者帶來(lái)變化的時(shí)候。一個(gè)結(jié)構(gòu)體的指針是非常常見(jiàn)的承載方法的類(lèi)型。
但是并不意味著只有指針類(lèi)型滿足接口類(lèi)型,甚至連一些有設(shè)置方法的接口類(lèi)型也可能會(huì)被Go語(yǔ)言中其它的引用類(lèi)型實(shí)現(xiàn)。我們已經(jīng)看過(guò)slice類(lèi)型的方法(geometry.Path,§6.1)和map類(lèi)型的方法(url.Values,§6.2.1),后面還會(huì)看到函數(shù)類(lèi)型的方法的例子(http.HandlerFunc,§7.7)。甚至基本的類(lèi)型也可能會(huì)實(shí)現(xiàn)一些接口;就如我們?cè)?.4章中看到的time.Duration類(lèi)型實(shí)現(xiàn)了fmt.Stringer接口。
一個(gè)具體的類(lèi)型可能實(shí)現(xiàn)了很多不相關(guān)的接口。考慮在一個(gè)組織出售數(shù)字文化產(chǎn)品比如音樂(lè),電影和書(shū)籍的程序中可能定義了下列的具體類(lèi)型:
Album
Book
Movie
Magazine
Podcast
TVEpisode
Track
我們可以把每個(gè)抽象的特點(diǎn)用接口來(lái)表示。一些特性對(duì)于所有的這些文化產(chǎn)品都是共通的,例如標(biāo)題,創(chuàng)作日期和作者列表。
type Artifact interface {
Title() string
Creators() []string
Created() time.Time
}
其它的一些特性只對(duì)特定類(lèi)型的文化產(chǎn)品才有。和文字排版特性相關(guān)的只有books和magazines,還有只有movies和TV劇集和屏幕分辨率相關(guān)。
type Text interface {
Pages() int
Words() int
PageSize() int
}
type Audio interface {
Stream() (io.ReadCloser, error)
RunningTime() time.Duration
Format() string // e.g., "MP3", "WAV"
}
type Video interface {
Stream() (io.ReadCloser, error)
RunningTime() time.Duration
Format() string // e.g., "MP4", "WMV"
Resolution() (x, y int)
}
這些接口不止是一種有用的方式來(lái)分組相關(guān)的具體類(lèi)型和表示他們之間的共同特點(diǎn)。我們后面可能會(huì)發(fā)現(xiàn)其它的分組。舉例,如果我們發(fā)現(xiàn)我們需要以同樣的方式處理Audio和Video,我們可以定義一個(gè)Streamer接口來(lái)代表它們之間相同的部分而不必對(duì)已經(jīng)存在的類(lèi)型做改變。
type Streamer interface {
Stream() (io.ReadCloser, error)
RunningTime() time.Duration
Format() string
}
每一個(gè)具體類(lèi)型的組基于它們相同的行為可以表示成一個(gè)接口類(lèi)型。不像基于類(lèi)的語(yǔ)言,他們一個(gè)類(lèi)實(shí)現(xiàn)的接口集合需要進(jìn)行顯式的定義,在Go語(yǔ)言中我們可以在需要的時(shí)候定義一個(gè)新的抽象或者特定特點(diǎn)的組,而不需要修改具體類(lèi)型的定義。當(dāng)具體的類(lèi)型來(lái)自不同的作者時(shí)這種方式會(huì)特別有用。當(dāng)然也確實(shí)沒(méi)有必要在具體的類(lèi)型中指出這些共性。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: