W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
原文鏈接:https://gopl-zh.github.io/ch7/ch7-03.html
一個(gè)類型如果擁有一個(gè)接口需要的所有方法,那么這個(gè)類型就實(shí)現(xiàn)了這個(gè)接口。例如,*os.File
類型實(shí)現(xiàn)了io.Reader,Writer,Closer,和ReadWriter接口。*bytes.Buffer
實(shí)現(xiàn)了Reader,Writer,和ReadWriter這些接口,但是它沒有實(shí)現(xiàn)Closer接口因?yàn)樗痪哂蠧lose方法。Go的程序員經(jīng)常會(huì)簡(jiǎn)要的把一個(gè)具體的類型描述成一個(gè)特定的接口類型。舉個(gè)例子,*bytes.Buffer
是io.Writer;*os.Files
是io.ReadWriter。
接口指定的規(guī)則非常簡(jiǎn)單:表達(dá)一個(gè)類型屬于某個(gè)接口只要這個(gè)類型實(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è)接口類型
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的類型必定也實(shí)現(xiàn)了Writer接口
在進(jìn)一步學(xué)習(xí)前,必須先解釋一個(gè)類型持有一個(gè)方法的表示當(dāng)中的細(xì)節(jié)?;叵朐?.2章中,對(duì)于每一個(gè)命名過的具體類型T;它的一些方法的接收者是類型T本身然而另一些則是一個(gè)*T
的指針。還記得在T類型的參數(shù)上調(diào)用一個(gè)*T
的方法是合法的,只要這個(gè)參數(shù)是一個(gè)變量;編譯器隱式的獲取了它的地址。但這僅僅是一個(gè)語法糖:T類型的值不擁有所有*T
指針的方法,這樣它就可能只實(shí)現(xiàn)了更少的接口。
舉個(gè)例子可能會(huì)更清晰一點(diǎn)。在第6.5章中,IntSet類型的String方法的接收者是一個(gè)指針類型,所以我們不能在一個(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
類型有String方法,所以也只有*IntSet
類型實(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è)類型的方法和具體類型和接口之間的關(guān)系
就像信封封裝和隱藏起信件來一樣,接口類型封裝和隱藏具體類型和它的值。即使具體類型有其它的方法,也只有接口類型暴露出來的方法會(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è)有更多方法的接口類型,比如io.ReadWriter,和少一些方法的接口類型例如io.Reader,進(jìn)行對(duì)比;更多方法的接口類型會(huì)告訴我們更多關(guān)于它的值持有的信息,并且對(duì)實(shí)現(xiàn)它的類型要求更加嚴(yán)格。那么關(guān)于interface{}類型,它沒有任何方法,請(qǐng)講出哪些具體的類型實(shí)現(xiàn)了它?
這看上去好像沒有用,但實(shí)際上interface{}被稱為空接口類型是不可或缺的。因?yàn)榭战涌陬愋蛯?duì)實(shí)現(xiàn)它的類型沒有要求,所以我們可以將任意一個(gè)值賦給空接口類型。
var any interface{}
any = true
any = 12.34
any = "hello"
any = map[string]int{"one": 1}
any = new(bytes.Buffer)
盡管不是很明顯,從本書最早的例子中我們就已經(jīng)在使用空接口類型。它允許像fmt.Println或者5.7章中的errorf函數(shù)接受任何類型的參數(shù)。
對(duì)于創(chuàng)建的一個(gè)interface{}值持有一個(gè)boolean,float,string,map,pointer,或者任意其它的類型;我們當(dāng)然不能直接對(duì)它持有的值做操作,因?yàn)閕nterface{}沒有任何方法。我們會(huì)在7.10章中學(xué)到一種用類型斷言來獲取interface{}中值的方法。
因?yàn)榻涌谂c實(shí)現(xiàn)只依賴于判斷兩個(gè)類型的方法,所以沒有必要定義一個(gè)具體類型和它實(shí)現(xiàn)的接口之間的關(guān)系。也就是說,有意地在文檔里說明或者程序上斷言這種關(guān)系偶爾是有用的,但程序上不強(qiáng)制這么做。下面的定義在編譯期斷言一個(gè)*bytes.Buffer
的值實(shí)現(xiàn)了io.Writer接口類型:
// *bytes.Buffer must satisfy io.Writer
var w io.Writer = new(bytes.Buffer)
因?yàn)槿我?code>*bytes.Buffer的值,甚至包括nil通過(*bytes.Buffer)(nil)
進(jìn)行顯示的轉(zhuǎn)換都實(shí)現(xiàn)了這個(gè)接口,所以我們不必分配一個(gè)新的變量。并且因?yàn)槲覀兘^不會(huì)引用變量w,我們可以使用空標(biāo)識(shí)符來進(jìn)行代替。總的看,這些變化可以讓我們得到一個(gè)更樸素的版本:
// *bytes.Buffer must satisfy io.Writer
var _ io.Writer = (*bytes.Buffer)(nil)
非空的接口類型比如io.Writer經(jīng)常被指針類型實(shí)現(xiàn),尤其當(dāng)一個(gè)或多個(gè)接口方法像Write方法那樣隱式的給接收者帶來變化的時(shí)候。一個(gè)結(jié)構(gòu)體的指針是非常常見的承載方法的類型。
但是并不意味著只有指針類型滿足接口類型,甚至連一些有設(shè)置方法的接口類型也可能會(huì)被Go語言中其它的引用類型實(shí)現(xiàn)。我們已經(jīng)看過slice類型的方法(geometry.Path,§6.1)和map類型的方法(url.Values,§6.2.1),后面還會(huì)看到函數(shù)類型的方法的例子(http.HandlerFunc,§7.7)。甚至基本的類型也可能會(huì)實(shí)現(xiàn)一些接口;就如我們?cè)?.4章中看到的time.Duration類型實(shí)現(xiàn)了fmt.Stringer接口。
一個(gè)具體的類型可能實(shí)現(xiàn)了很多不相關(guān)的接口??紤]在一個(gè)組織出售數(shù)字文化產(chǎn)品比如音樂,電影和書籍的程序中可能定義了下列的具體類型:
Album
Book
Movie
Magazine
Podcast
TVEpisode
Track
我們可以把每個(gè)抽象的特點(diǎn)用接口來表示。一些特性對(duì)于所有的這些文化產(chǎn)品都是共通的,例如標(biāo)題,創(chuàng)作日期和作者列表。
type Artifact interface {
Title() string
Creators() []string
Created() time.Time
}
其它的一些特性只對(duì)特定類型的文化產(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)
}
這些接口不止是一種有用的方式來分組相關(guān)的具體類型和表示他們之間的共同特點(diǎn)。我們后面可能會(huì)發(fā)現(xiàn)其它的分組。舉例,如果我們發(fā)現(xiàn)我們需要以同樣的方式處理Audio和Video,我們可以定義一個(gè)Streamer接口來代表它們之間相同的部分而不必對(duì)已經(jīng)存在的類型做改變。
type Streamer interface {
Stream() (io.ReadCloser, error)
RunningTime() time.Duration
Format() string
}
每一個(gè)具體類型的組基于它們相同的行為可以表示成一個(gè)接口類型。不像基于類的語言,他們一個(gè)類實(shí)現(xiàn)的接口集合需要進(jìn)行顯式的定義,在Go語言中我們可以在需要的時(shí)候定義一個(gè)新的抽象或者特定特點(diǎn)的組,而不需要修改具體類型的定義。當(dāng)具體的類型來自不同的作者時(shí)這種方式會(huì)特別有用。當(dāng)然也確實(shí)沒有必要在具體的類型中指出這些共性。
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)系方式:
更多建議: