mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2026-01-16 12:17:13 +08:00
回到简体
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
## 7.3. 實現接口的條件
|
||||
## 7.3. 实现接口的条件
|
||||
|
||||
一個類型如果擁有一個接口需要的所有方法,那麽這個類型就實現了這個接口。例如,\*os.File類型實現了io.Reader,Writer,Closer,和ReadWriter接口。\*bytes.Buffer實現了Reader,Writer,和ReadWriter這些接口,但是它沒有實現Closer接口因爲它不具有Close方法。Go的程序員經常會簡要的把一個具體的類型描述成一個特定的接口類型。舉個例子,\*bytes.Buffer是io.Writer;\*os.Files是io.ReadWriter。
|
||||
一个类型如果拥有一个接口需要的所有方法,那么这个类型就实现了这个接口。例如,\*os.File类型实现了io.Reader,Writer,Closer,和ReadWriter接口。\*bytes.Buffer实现了Reader,Writer,和ReadWriter这些接口,但是它没有实现Closer接口因为它不具有Close方法。Go的程序员经常会简要的把一个具体的类型描述成一个特定的接口类型。举个例子,\*bytes.Buffer是io.Writer;\*os.Files是io.ReadWriter。
|
||||
|
||||
接口指定的規則非常簡單:表達一個類型屬於某個接口隻要這個類型實現這個接口。所以:
|
||||
接口指定的规则非常简单:表达一个类型属于某个接口只要这个类型实现这个接口。所以:
|
||||
|
||||
```go
|
||||
var w io.Writer
|
||||
@@ -15,18 +15,18 @@ rwc = os.Stdout // OK: *os.File has Read, Write, Close methods
|
||||
rwc = new(bytes.Buffer) // compile error: *bytes.Buffer lacks Close method
|
||||
```
|
||||
|
||||
這個規則甚至適用於等式右邊本身也是一個接口類型
|
||||
这个规则甚至适用于等式右边本身也是一个接口类型
|
||||
|
||||
```go
|
||||
w = rwc // OK: io.ReadWriteCloser has Write method
|
||||
rwc = w // compile error: io.Writer lacks Close method
|
||||
```
|
||||
|
||||
因爲ReadWriter和ReadWriteCloser包含所有Writer的方法,所以任何實現了ReadWriter和ReadWriteCloser的類型必定也實現了Writer接口
|
||||
因为ReadWriter和ReadWriteCloser包含所有Writer的方法,所以任何实现了ReadWriter和ReadWriteCloser的类型必定也实现了Writer接口
|
||||
|
||||
在進一步學習前,必須先解釋表示一個類型持有一個方法當中的細節。迴想在6.2章中,對於每一個命名過的具體類型T;它一些方法的接收者是類型T本身然而另一些則是一個*T的指針。還記得在T類型的參數上調用一個*T的方法是合法的,隻要這個參數是一個變量;編譯器隱式的獲取了它的地址。但這僅僅是一個語法糖:T類型的值不擁有所有*T指針的方法,那這樣它就可能隻實現更少的接口。
|
||||
在进一步学习前,必须先解释表示一个类型持有一个方法当中的细节。回想在6.2章中,对于每一个命名过的具体类型T;它一些方法的接收者是类型T本身然而另一些则是一个*T的指针。还记得在T类型的参数上调用一个*T的方法是合法的,只要这个参数是一个变量;编译器隐式的获取了它的地址。但这仅仅是一个语法糖:T类型的值不拥有所有*T指针的方法,那这样它就可能只实现更少的接口。
|
||||
|
||||
舉個例子可能會更清晰一點。在第6.5章中,IntSet類型的String方法的接收者是一個指針類型,所以我們不能在一個不能尋址的IntSet值上調用這個方法:
|
||||
举个例子可能会更清晰一点。在第6.5章中,IntSet类型的String方法的接收者是一个指针类型,所以我们不能在一个不能寻址的IntSet值上调用这个方法:
|
||||
|
||||
```go
|
||||
type IntSet struct { /* ... */ }
|
||||
@@ -34,23 +34,23 @@ func (*IntSet) String() string
|
||||
var _ = IntSet{}.String() // compile error: String requires *IntSet receiver
|
||||
```
|
||||
|
||||
但是我們可以在一個IntSet值上調用這個方法:
|
||||
但是我们可以在一个IntSet值上调用这个方法:
|
||||
|
||||
```go
|
||||
var s IntSet
|
||||
var _ = s.String() // OK: s is a variable and &s has a String method
|
||||
```
|
||||
|
||||
然而,由於隻有*IntSet類型有String方法,所有也隻有*IntSet類型實現了fmt.Stringer接口:
|
||||
然而,由于只有*IntSet类型有String方法,所有也只有*IntSet类型实现了fmt.Stringer接口:
|
||||
|
||||
```go
|
||||
var _ fmt.Stringer = &s // OK
|
||||
var _ fmt.Stringer = s // compile error: IntSet lacks String method
|
||||
```
|
||||
|
||||
12.8章包含了一個打印出任意值的所有方法的程序,然後可以使用godoc -analysis=type tool(§10.7.4)展示每個類型的方法和具體類型和接口之間的關繫
|
||||
12.8章包含了一个打印出任意值的所有方法的程序,然后可以使用godoc -analysis=type tool(§10.7.4)展示每个类型的方法和具体类型和接口之间的关系
|
||||
|
||||
就像信封封裝和隱藏信件起來一樣,接口類型封裝和隱藏具體類型和它的值。卽使具體類型有其它的方法也隻有接口類型暴露出來的方法會被調用到:
|
||||
就像信封封装和隐藏信件起来一样,接口类型封装和隐藏具体类型和它的值。即使具体类型有其它的方法也只有接口类型暴露出来的方法会被调用到:
|
||||
|
||||
```go
|
||||
os.Stdout.Write([]byte("hello")) // OK: *os.File has Write method
|
||||
@@ -62,9 +62,9 @@ w.Write([]byte("hello")) // OK: io.Writer has Write method
|
||||
w.Close() // compile error: io.Writer lacks Close method
|
||||
```
|
||||
|
||||
一個有更多方法的接口類型,比如io.ReadWriter,和少一些方法的接口類型,例如io.Reader,進行對比;更多方法的接口類型會告訴我們更多關於它的值持有的信息,併且對實現它的類型要求更加嚴格。那麽關於interface{}類型,它沒有任何方法,請講出哪些具體的類型實現了它?
|
||||
一个有更多方法的接口类型,比如io.ReadWriter,和少一些方法的接口类型,例如io.Reader,进行对比;更多方法的接口类型会告诉我们更多关于它的值持有的信息,并且对实现它的类型要求更加严格。那么关于interface{}类型,它没有任何方法,请讲出哪些具体的类型实现了它?
|
||||
|
||||
這看上去好像沒有用,但實際上interface{}被稱爲空接口類型是不可或缺的。因爲空接口類型對實現它的類型沒有要求,所以我們可以將任意一個值賦給空接口類型。
|
||||
这看上去好像没有用,但实际上interface{}被称为空接口类型是不可或缺的。因为空接口类型对实现它的类型没有要求,所以我们可以将任意一个值赋给空接口类型。
|
||||
|
||||
```go
|
||||
var any interface{}
|
||||
@@ -75,29 +75,29 @@ any = map[string]int{"one": 1}
|
||||
any = new(bytes.Buffer)
|
||||
```
|
||||
|
||||
盡管不是很明顯,從本書最早的的例子中我們就已經在使用空接口類型。它允許像fmt.Println或者5.7章中的errorf函數接受任何類型的參數。
|
||||
尽管不是很明显,从本书最早的的例子中我们就已经在使用空接口类型。它允许像fmt.Println或者5.7章中的errorf函数接受任何类型的参数。
|
||||
|
||||
對於創建的一個interface{}值持有一個boolean,float,string,map,pointer,或者任意其它的類型;我們當然不能直接對它持有的值做操作,因爲interface{}沒有任何方法。我們會在7.10章中學到一種用類型斷言來獲取interface{}中值的方法。
|
||||
对于创建的一个interface{}值持有一个boolean,float,string,map,pointer,或者任意其它的类型;我们当然不能直接对它持有的值做操作,因为interface{}没有任何方法。我们会在7.10章中学到一种用类型断言来获取interface{}中值的方法。
|
||||
|
||||
因爲接口實現隻依賴於判斷的兩個類型的方法,所以沒有必要定義一個具體類型和它實現的接口之間的關繫。也就是説,嚐試文檔化和斷言這種關繫幾乎沒有用,所以併沒有通過程序強製定義。下面的定義在編譯期斷言一個*bytes.Buffer的值實現了io.Writer接口類型:
|
||||
因为接口实现只依赖于判断的两个类型的方法,所以没有必要定义一个具体类型和它实现的接口之间的关系。也就是说,尝试文档化和断言这种关系几乎没有用,所以并没有通过程序强制定义。下面的定义在编译期断言一个*bytes.Buffer的值实现了io.Writer接口类型:
|
||||
|
||||
```go
|
||||
// *bytes.Buffer must satisfy io.Writer
|
||||
var w io.Writer = new(bytes.Buffer)
|
||||
```
|
||||
|
||||
因爲任意*bytes.Buffer的值,甚至包括nil通過(*bytes.Buffer)(nil)進行顯示的轉換都實現了這個接口,所以我們不必分配一個新的變量。併且因爲我們絶不會引用變量w,我們可以使用空標識符來來進行代替。總的看,這些變化可以讓我們得到一個更樸素的版本:
|
||||
因为任意*bytes.Buffer的值,甚至包括nil通过(*bytes.Buffer)(nil)进行显示的转换都实现了这个接口,所以我们不必分配一个新的变量。并且因为我们绝不会引用变量w,我们可以使用空标识符来来进行代替。总的看,这些变化可以让我们得到一个更朴素的版本:
|
||||
|
||||
```go
|
||||
// *bytes.Buffer must satisfy io.Writer
|
||||
var _ io.Writer = (*bytes.Buffer)(nil)
|
||||
```
|
||||
|
||||
非空的接口類型比如io.Writer經常被指針類型實現,尤其當一個或多個接口方法像Write方法那樣隱式的給接收者帶來變化的時候。一個結構體的指針是非常常見的承載方法的類型。
|
||||
非空的接口类型比如io.Writer经常被指针类型实现,尤其当一个或多个接口方法像Write方法那样隐式的给接收者带来变化的时候。一个结构体的指针是非常常见的承载方法的类型。
|
||||
|
||||
但是併不意味着隻有指針類型滿足接口類型,甚至連一些有設置方法的接口類型也可能會被Go語言中其它的引用類型實現。我們已經看過slice類型的方法(geometry.Path, §6.1)和map類型的方法(url.Values, §6.2.1),後面還會看到函數類型的方法的例子(http.HandlerFunc, §7.7)。甚至基本的類型也可能會實現一些接口;就如我們在7.4章中看到的time.Duration類型實現了fmt.Stringer接口。
|
||||
但是并不意味着只有指针类型满足接口类型,甚至连一些有设置方法的接口类型也可能会被Go语言中其它的引用类型实现。我们已经看过slice类型的方法(geometry.Path, §6.1)和map类型的方法(url.Values, §6.2.1),后面还会看到函数类型的方法的例子(http.HandlerFunc, §7.7)。甚至基本的类型也可能会实现一些接口;就如我们在7.4章中看到的time.Duration类型实现了fmt.Stringer接口。
|
||||
|
||||
一個具體的類型可能實現了很多不相關的接口。考慮在一個組織出售數字文化産品比如音樂,電影和書籍的程序中可能定義了下列的具體類型:
|
||||
一个具体的类型可能实现了很多不相关的接口。考虑在一个组织出售数字文化产品比如音乐,电影和书籍的程序中可能定义了下列的具体类型:
|
||||
|
||||
```
|
||||
Album
|
||||
@@ -109,7 +109,7 @@ TVEpisode
|
||||
Track
|
||||
```
|
||||
|
||||
我們可以把每個抽象的特點用接口來表示。一些特性對於所有的這些文化産品都是共通的,例如標題,創作日期和作者列表。
|
||||
我们可以把每个抽象的特点用接口来表示。一些特性对于所有的这些文化产品都是共通的,例如标题,创作日期和作者列表。
|
||||
|
||||
```go
|
||||
type Artifact interface {
|
||||
@@ -118,7 +118,7 @@ type Artifact interface {
|
||||
Created() time.Time
|
||||
}
|
||||
```
|
||||
其它的一些特性隻對特定類型的文化産品才有。和文字排版特性相關的隻有books和magazines,還有隻有movies和TV劇集和屏幕分辨率相關。
|
||||
其它的一些特性只对特定类型的文化产品才有。和文字排版特性相关的只有books和magazines,还有只有movies和TV剧集和屏幕分辨率相关。
|
||||
|
||||
```go
|
||||
type Text interface {
|
||||
@@ -139,7 +139,7 @@ type Video interface {
|
||||
}
|
||||
```
|
||||
|
||||
這些接口不止是一種有用的方式來分組相關的具體類型和表示他們之間的共同特定。我們後面可能會發現其它的分組。舉例,如果我們發現我們需要以同樣的方式處理Audio和Video,我們可以定義一個Streamer接口來代表它們之間相同的部分而不必對已經存在的類型做改變。
|
||||
这些接口不止是一种有用的方式来分组相关的具体类型和表示他们之间的共同特定。我们后面可能会发现其它的分组。举例,如果我们发现我们需要以同样的方式处理Audio和Video,我们可以定义一个Streamer接口来代表它们之间相同的部分而不必对已经存在的类型做改变。
|
||||
|
||||
```go
|
||||
type Streamer interface {
|
||||
@@ -149,4 +149,4 @@ type Streamer interface {
|
||||
}
|
||||
```
|
||||
|
||||
每一個具體類型的組基於它們相同的行爲可以表示成一個接口類型。不像基於類的語言,他們一個類實現的接口集合需要進行顯式的定義,在Go語言中我們可以在需要的時候定義一個新的抽象或者特定特點的組,而不需要脩改具體類型的定義。當具體的類型來自不同的作者時這種方式會特别有用。當然也確實沒有必要在具體的類型中指出這些共性。
|
||||
每一个具体类型的组基于它们相同的行为可以表示成一个接口类型。不像基于类的语言,他们一个类实现的接口集合需要进行显式的定义,在Go语言中我们可以在需要的时候定义一个新的抽象或者特定特点的组,而不需要修改具体类型的定义。当具体的类型来自不同的作者时这种方式会特别有用。当然也确实没有必要在具体的类型中指出这些共性。
|
||||
|
||||
Reference in New Issue
Block a user