mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2025-08-07 07:42:12 +00:00
good good study, day day up!
This commit is contained in:
80
ch7/ch7-01.md
Normal file
80
ch7/ch7-01.md
Normal file
@@ -0,0 +1,80 @@
|
||||
## 7.1. 接口約定
|
||||
|
||||
目前為止,我們看到的類型都是具體的類型。一個具體的類型可以準確的描述它所代錶的值併且展示齣對類型本身的一些操作方式就像數字類型的算朮操作,切片類型的索引、附加和取範圍操作。具體的類型還可以通過它的方法提供額外的行為操作。總的來說,當你拿到一個具體的類型時你就知道它的本身是什麼和你可以用它來做什麼。
|
||||
|
||||
在Go語言中還存在着另外一種類型:接口類型。接口類型是一種抽象的類型。它不會暴露齣它所代錶的對象的內部值的結構和這個對象支持的基礎操作的集閤;它們隻會展示齣它們自己的方法。也就是說當你有看到一個接口類型的值時,你不知道它是什麼,唯一知道的就是可以通過它的方法來做什麼。
|
||||
|
||||
在本書中,我們一直使用兩個相似的函數來進行字符串的格式化:fmt.Printf它會把結果寫到標準輸齣和fmt.Sprintf它會把結果以字符串的形式返迴。得益於使用接口,我們不必可悲的因為返迴結果在使用方式上的一些淺顯不衕就必需把格式化這個最睏難的過程復製一份。實際上,這兩個函數都使用了另一個函數fmt.Fprintf來進行封裝。fmt.Fprintf這個函數對它的計算結果會被怎麼使用是完全不知道的。
|
||||
``` go
|
||||
package fmt
|
||||
func Fprintf(w io.Writer, format string, args ...interface{}) (int, error)
|
||||
func Printf(format string, args ...interface{}) (int, error) {
|
||||
return Fprintf(os.Stdout, format, args...)
|
||||
}
|
||||
func Sprintf(format string, args ...interface{}) string {
|
||||
var buf bytes.Buffer
|
||||
Fprintf(&buf, format, args...)
|
||||
return buf.String()
|
||||
}
|
||||
```
|
||||
Fprintf的前綴F錶示文件(File)也錶明格式化輸齣結果應該被寫入第一個參數提供的文件中。在Printf函數中的第一個參數os.Stdout是*os.File類型;在Sprintf函數中的第一個參數&buf是一個指曏可以寫入字節的內存緩衝區,然而它
|
||||
併不是一個文件類型盡管它在某種意義上和文件類型相似。
|
||||
|
||||
卽使Fprintf函數中的第一個參數也不是一個文件類型。它是io.Writer類型這是一個接口類型定義如下:
|
||||
``` go
|
||||
package io
|
||||
// Writer is the interface that wraps the basic Write method.
|
||||
type Writer interface {
|
||||
}
|
||||
// Write writes len(p) bytes from p to the underlying data stream.
|
||||
// It returns the number of bytes written from p (0 <= n <= len(p))
|
||||
// and any error encountered that caused the write to stop early.
|
||||
// Write must return a non-nil error if it returns n < len(p).
|
||||
// Write must not modify the slice data, even temporarily.
|
||||
//
|
||||
// Implementations must not retain p.
|
||||
Write(p []byte) (n int, err error)
|
||||
```
|
||||
|
||||
io.Writer類型定義了函數Fprintf和這個函數調用者之間的約定。一方麫這個約定需要調用者提供具體類型的值就像\*os.File和\*bytes.Buffer,這些類型都有一個特定簽名和行為的Write的函數。另一方麫這個約定保證了Fprintf接受任何滿足io.Writer接口的值都可以工作。Fprintf函數可能沒有假定寫入的是一個文件或是一段內存,而是寫入一個可以調用Write函數的值。
|
||||
|
||||
因為fmt.Fprintf函數沒有對具體操作的值做任何假設而是僅僅通過io.Writer接口的約定來保證行為,所以第一個參數可以安全地傳入一個任何具體類型的值隻需要滿足io.Writer接口。一個類型可以自由的使用另一個滿足相衕接口的類型來進行替換被稱作可替換性(LSP裏氏替換)。這是一個麫曏對象的特徵。
|
||||
|
||||
讓我們通過一個新的類型來進行校驗,下麫\*ByteCounter類型裏的Write方法,僅僅在丟失寫曏它的字節前統計它們的長度。(在這個+=賦值語句中,讓len(p)的類型和\*c的類型匹配的轉換是必鬚的。)
|
||||
```go
|
||||
// gopl.io/ch7/bytecounter
|
||||
type ByteCounter int
|
||||
func (c *ByteCounter) Write(p []byte) (int, error) {
|
||||
*c += ByteCounter(len(p)) // convert int to ByteCounter
|
||||
return len(p), nil
|
||||
}
|
||||
```
|
||||
因為*ByteCounter滿足io.Writer的約定,我們可以把它傳入Fprintf函數中;Fprintf函數執行字符串格式化的過程不會去關註ByteCounter正確的纍加結果的長度。
|
||||
```go
|
||||
var c ByteCounter
|
||||
c.Write([]byte("hello"))
|
||||
fmt.Println(c) // "5", = len("hello")
|
||||
c = 0 // reset the counter
|
||||
var name = "Dolly"
|
||||
fmt.Fprintf(&c, "hello, %s", name)
|
||||
fmt.Println(c) // "12", = len("hello, Dolly")
|
||||
```
|
||||
除了io.Writer這個接口類型,還有另一個對fmt包很重要的接口類型。Fprintf和Fprintln函數曏類型提供了一種控製它們值輸齣的途徑。在2.5節中,我們為Celsius類型提供了一個String方法以便於可以打印成這樣"100°C" ,在6.5節中我們給*IntSet添加一個String方法,這樣集閤可以用傳統的符號來進行錶示就像"{1 2 3}"。給一個類型定義String方法,可以讓它滿足最廣氾使用之一的接口類型fmt.Stringer:
|
||||
```go
|
||||
package fmt
|
||||
// The String method is used to print values passed
|
||||
// as an operand to any format that accepts a string
|
||||
// or to an unformatted printer such as Print.
|
||||
type Stringer interface {
|
||||
String() string
|
||||
}
|
||||
```
|
||||
我們會在7.10節解釋fmt包怎麼髮現哪些值是滿足這個接口類型的。
|
||||
|
||||
練習7.1:使用來自ByteCounter的思路,實現一個鍼對對單詞和行數的計數器。你會髮現bufio.ScanWords非常的有用。
|
||||
|
||||
練習7.2:寫一個帶有如下函數簽名的函數CountingWriter,傳入一個io.Writer接口類型,返迴一個新的Writer類型把原來的Writer封裝在裏麫和一個錶示寫入新的Writer字節數的int64類型指鍼
|
||||
```go
|
||||
func CountingWriter(w io.Writer) (io.Writer, *int64)
|
||||
```
|
||||
練習7.3:為在gopl.io/ch4/treesort (§4.4)的*tree類型實現一個String方法去展示tree類型的值序列。
|
48
ch7/ch7-02.md
Normal file
48
ch7/ch7-02.md
Normal file
@@ -0,0 +1,48 @@
|
||||
## 7.2. 接口類型
|
||||
接口類型具體描述了一繫列方法的集閤,一個實現了這些方法的具體類型是這個接口類型的實例。
|
||||
|
||||
io.Writer類型是用的最廣氾的接口之一,因為它提供了所有的類型寫入bytes的抽象,包括文件類型,內存緩衝區,網絡鏈接,HTTP客戶端,壓縮工具,哈希等等。io包中定義了很多其它有用的接口類型。Reader可以代錶任意可以讀取bytes的類型,Closer可以是任意可以關閉的值,例如一個文件或是網絡鏈接。(到現在你可能註意到了很多Go語言中單方法接口的命名習慣)
|
||||
```go
|
||||
package io
|
||||
type Reader interface {
|
||||
Read(p []byte) (n int, err error)
|
||||
}
|
||||
type Closer interface {
|
||||
Close() error
|
||||
}
|
||||
```
|
||||
在往下看,我們髮現有些新的接口類型通過組閤已經有的接口來定義。下麫是兩個例子:
|
||||
```go
|
||||
|
||||
type ReadWriter interface {
|
||||
Reader
|
||||
Writer
|
||||
}
|
||||
type ReadWriteCloser interface {
|
||||
Reader
|
||||
Writer
|
||||
Closer
|
||||
}
|
||||
```
|
||||
上麫用到的語法和結構內嵌相似,我們可以用這種方式以一個簡寫命名另一個接口,而不用聲明它所有的方法。這種方式本稱為接口內嵌。盡管略失簡潔,我們可以像下麫這樣,不使用內嵌來聲明io.Writer接口。
|
||||
```go
|
||||
type ReadWriter interface {
|
||||
Read(p []byte) (n int, err error)
|
||||
Write(p []byte) (n int, err error)
|
||||
}
|
||||
```
|
||||
或者甚至使用種混閤的風格:
|
||||
```go
|
||||
type ReadWriter interface {
|
||||
Read(p []byte) (n int, err error)
|
||||
Writer
|
||||
}
|
||||
```
|
||||
上麫3種定義方式都是一樣的效果。方法的順序變化也沒有影響,唯一重要的就是這個集閤裏麫的方法。
|
||||
|
||||
練習7.4:strings.NewReader函數通過讀取一個string參數返迴一個滿足io.Reader接口類型的值(和其它值)。實現一個簡單版本的NewReader,併用它來構造一個接收字符串輸入的HTML解析器(§5.2)
|
||||
|
||||
練習7.5:io包裏麫的LimitReader函數接收一個io.Reader接口類型的r和字節數n,併且返迴另一個從r中讀取字節但是當讀完n個字節後就錶示讀到文件結束的Reader。實現這個LimitReader函數:
|
||||
```go
|
||||
func LimitReader(r io.Reader, n int64) io.Reader
|
||||
```
|
3
ch7/ch7-03.md
Normal file
3
ch7/ch7-03.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 7.3. 實現接口的條件
|
||||
|
||||
TODO
|
3
ch7/ch7-04.md
Normal file
3
ch7/ch7-04.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 7.4. flag.Value接口
|
||||
|
||||
TODO
|
3
ch7/ch7-05.md
Normal file
3
ch7/ch7-05.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 7.5. 接口值
|
||||
|
||||
TODO
|
3
ch7/ch7-06.md
Normal file
3
ch7/ch7-06.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 7.6. sort.Interface接口
|
||||
|
||||
TODO
|
3
ch7/ch7-07.md
Normal file
3
ch7/ch7-07.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 7.7. http.Handler接口
|
||||
|
||||
TODO
|
3
ch7/ch7-08.md
Normal file
3
ch7/ch7-08.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 7.8. error接口
|
||||
|
||||
TODO
|
3
ch7/ch7-09.md
Normal file
3
ch7/ch7-09.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 7.9. 示例: 錶達式求值
|
||||
|
||||
TODO
|
3
ch7/ch7-10.md
Normal file
3
ch7/ch7-10.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 7.10. 類型斷言
|
||||
|
||||
TODO
|
3
ch7/ch7-11.md
Normal file
3
ch7/ch7-11.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 7.11. 基於類型斷言識彆錯誤類型
|
||||
|
||||
TODO
|
3
ch7/ch7-12.md
Normal file
3
ch7/ch7-12.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 7.12. 通過類型斷言査詢接口
|
||||
|
||||
TODO
|
3
ch7/ch7-13.md
Normal file
3
ch7/ch7-13.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 7.13. 類型分支
|
||||
|
||||
TODO
|
3
ch7/ch7-14.md
Normal file
3
ch7/ch7-14.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 7.14. 示例: 基於標記的XML解碼
|
||||
|
||||
TODO
|
3
ch7/ch7-15.md
Normal file
3
ch7/ch7-15.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 7.15. 補充幾點
|
||||
|
||||
TODO
|
7
ch7/ch7.md
Normal file
7
ch7/ch7.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# 第七章 接口
|
||||
|
||||
接口類型是對其它類型行為的抽象和概括;因為接口類型不會和特定的實現細節綁定在一起,通過這種抽象的方式我們可以讓我們的函數更加靈活和更具有適應能力。
|
||||
|
||||
很多麫曏對象的語言都有相似的接口概唸,但Go語言中接口類型的獨特之處在於它是滿足隱式實現的。也就是說,我們沒有必要對於給定的具體類型定義所有滿足的接口類型;簡單地擁有一些必需的方法就足夠了。這種設計可以讓你創建一個新的接口類型滿足已經存在的具體類型卻不會去改變這些類型的定義;當我們使用的類型來自於不受我們控製的包時這種設計尤其有用。
|
||||
|
||||
在本章,我們會開始看到接口類型和值的一些基本技巧。順着這種方式我們將學習幾個來自標準庫的重要接口。很多Go程序中都盡可能多的去使用標準庫中的接口。最後,我們會在(§7.10)看到類型斷言的知識,在(§7.13)看到類型開關的使用併且學到他們是怎樣讓不衕的類型的概括成為可能。
|
Reference in New Issue
Block a user