This commit is contained in:
chai2010
2016-01-02 21:17:21 +08:00
parent 8772a9c000
commit ba03c527c0
17 changed files with 63 additions and 29 deletions

View File

@@ -5,6 +5,7 @@
在Go語言中還存在着另外一種類型接口類型。接口類型是一種抽象的類型。它不會暴露出它所代表的對象的內部值的結構和這個對象支持的基礎操作的集合它們隻會展示出它們自己的方法。也就是説當你有看到一個接口類型的值時你不知道它是什麽唯一知道的就是可以通過它的方法來做什麽。
在本書中我們一直使用兩個相似的函數來進行字符串的格式化fmt.Printf它會把結果寫到標準輸出和fmt.Sprintf它會把結果以字符串的形式返迴。得益於使用接口我們不必可悲的因爲返迴結果在使用方式上的一些淺顯不同就必需把格式化這個最睏難的過程複製一份。實際上這兩個函數都使用了另一個函數fmt.Fprintf來進行封裝。fmt.Fprintf這個函數對它的計算結果會被怎麽使用是完全不知道的。
``` go
package fmt
func Fprintf(w io.Writer, format string, args ...interface{}) (int, error)
@@ -17,10 +18,12 @@ func Sprintf(format string, args ...interface{}) string {
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.
@@ -41,6 +44,7 @@ io.Writer類型定義了函數Fprintf和這個函數調用者之間的約定。
因爲fmt.Fprintf函數沒有對具體操作的值做任何假設而是僅僅通過io.Writer接口的約定來保證行爲所以第一個參數可以安全地傳入一個任何具體類型的值隻需要滿足io.Writer接口。一個類型可以自由的使用另一個滿足相同接口的類型來進行替換被稱作可替換性(LSP里氏替換)。這是一個面向對象的特徵。
讓我們通過一個新的類型來進行校驗,下面\*ByteCounter類型里的Write方法僅僅在丟失寫向它的字節前統計它們的長度。(在這個+=賦值語句中讓len(p)的類型和\*c的類型匹配的轉換是必鬚的。)
```go
// gopl.io/ch7/bytecounter
type ByteCounter int
@@ -50,6 +54,7 @@ func (c *ByteCounter) Write(p []byte) (int, error) {
}
```
因爲*ByteCounter滿足io.Writer的約定我們可以把它傳入Fprintf函數中Fprintf函數執行字符串格式化的過程不會去關註ByteCounter正確的纍加結果的長度。
```go
var c ByteCounter
c.Write([]byte("hello"))
@@ -59,7 +64,9 @@ 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
@@ -69,12 +76,15 @@ 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類型的值序列。

View File

@@ -2,6 +2,7 @@
接口類型具體描述了一繫列方法的集合,一個實現了這些方法的具體類型是這個接口類型的實例。
io.Writer類型是用的最廣泛的接口之一因爲它提供了所有的類型寫入bytes的抽象包括文件類型內存緩衝區網絡鏈接HTTP客戶端壓縮工具哈希等等。io包中定義了很多其它有用的接口類型。Reader可以代表任意可以讀取bytes的類型Closer可以是任意可以關閉的值例如一個文件或是網絡鏈接。到現在你可能註意到了很多Go語言中單方法接口的命名習慣
```go
package io
type Reader interface {
@@ -11,9 +12,10 @@ type Closer interface {
Close() error
}
```
在往下看,我們發現有些新的接口類型通過組合已經有的接口來定義。下面是兩個例子:
```go
在往下看,我們發現有些新的接口類型通過組合已經有的接口來定義。下面是兩個例子:
```go
type ReadWriter interface {
Reader
Writer
@@ -25,24 +27,29 @@ type ReadWriteCloser interface {
}
```
上面用到的語法和結構內嵌相似我們可以用這種方式以一個簡寫命名另一個接口而不用聲明它所有的方法。這種方式本稱爲接口內嵌。盡管略失簡潔我們可以像下面這樣不使用內嵌來聲明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
```