# Day 27 : Golang interface(5) - io.Reader & io.Writer
由於想更瞭解我們實際使用不同package的過程中是如何使用到interface,這裡想專門跟著Udemy課程做個學習紀錄。
目標是要透過http.Get取得response的body資料,根據官方文件的Get函式開始進行Document的閱讀,其步驟如下,會發現我們要看到第4步,才能瞭解要透過Read方法取得body資料。
1. Get函式定義
`func Get(url string) (resp *Response, err error)`
2. resp為一個指向Response物件的指針,Response結構體定義如下
```
type Response struct {
Status string // e.g. "200 OK"
StatusCode int // e.g. 200
Proto string // e.g. "HTTP/1.0"
ProtoMajor int // e.g. 1
ProtoMinor int // e.g. 0
Header Header
Body io.ReadCloser
...略}
```
3. ReadCloser唯一個interface屬於io package,定義如下
```
type ReadCloser interface {
Reader
Closer
}
```
4. Reader介面定義如下
```
type Reader interface {
Read(p []byte) (n int, err error)
}
```
5. Closer介面定義如下
```
type Closer interface {
Close() error
}
```

(圖一,圖片來源 : [Go: The Complete Developer's Guide (Golang)](https://www.udemy.com/course/go-the-complete-developers-guide/))
這裡可以看到ReadCloser介面,定義Reader介面和Closer介面,也就是說任何實現ReadCloser 介面的型別,也必須同時實現Reader介面和Closer介面。總結來說這邊可以看到Body其實可以是任意型別的值,只要有方法是符合Reader 介面定義的Read()和Closer interface定義的Close()。
## Reader interface and Read function
在Go裡面Reader介面其實可以用在很多地方,不單單只是就是用來取得Body資料,以圖二來說,如果有許多不同來源的input,會需要建立多個接受不同型別作為參數的function,然後產生不同型別的output。但如果使用Reader介面,會發現不管你的來源是什麼資料,只要你的型別實現了Reader介面,就可以讀取內容了

(圖二,圖片來源 : [Go: The Complete Developer's Guide (Golang)](https://www.udemy.com/course/go-the-complete-developers-guide/))

(圖三,圖片來源 : [Go: The Complete Developer's Guide (Golang)](https://www.udemy.com/course/go-the-complete-developers-guide/))
那什麼又是Read function呢,其原理比較複雜,課程中透過圖四來介紹。
在圖四最左邊,代表誰想要實現Reader interface,就要先建立屬於自己的byte slice,這次的例子就可以看成我們想要透過Read function 讀取body的資料。
創造一個byte slice後,會將其傳入給實現Reader interface的型別,要如何實現Reader interface呢,就是要建立Read function。所以就是要建立一個byte slice傳入Read function,當Read函式取得response body之後,就會將資料寫入到byte slice。
接下來看回傳值,err比較單純就是用來做錯誤處裡,而n我們可以先看看[官方文件](https://pkg.go.dev/io#Reader)的定義
> *Read reads up to `len(p)` bytes into p. It returns the number of bytes read (0 <= n <= `len(p)`) and any error encountered. *
也就是說n代表Read函式讀取到的byte值,n如果大於0代表讀取到有效的內容,會繼續執行。一般來說Read函式會其實是一直被使用,每次會讀取一定量的byte,存到我們建立的byte slice,直到Read函數返回的n為0,代表已讀取完全部的資料。
至於為什麼要回傳一個整數n,不直接回傳byte slice呢?這邊我其實還不是太瞭解,但根據這篇[Reddit](https://www.reddit.com/r/golang/comments/uuu52z/readerreadbyte_design/)的回答,就是為了讓使用者可以透過緩衝區重複使用來有效利用memory,如果返回的是一個slice,每次使用都要重新分配memory,可能就會造成浪費。
舉例來說,讀取一個1GB的文件時,可以使用1MB的緩衝區進行讀取和處理,這樣可以重複使用緩衝區,而不需要每次都重新分配1GB的memory。

(圖四,圖片來源 : [Go: The Complete Developer's Guide (Golang)](https://www.udemy.com/course/go-the-complete-developers-guide/))
## 程式碼實作
說這麼多,還是回到程式碼實作,看是如何一步步實踐上面的說明。
一開始透過http.Get取得一個指向Resonpse結構體的指針resp。這個結構體有定義一個Body,型別為io.ReadCloser介面。io.ReadCloser介面同時又滿足Read介面和Closer介面,所以可以使用Read函式和Close函式。
所以我們先定義一個很大的byte slice,然後傳入Read函式。當執行完Read函式時,byte slice就已經被更新完成,透過fmt.Println就可以得到我們要的資訊
```go=
func main() {
resp, err := http.Get("http://google.com/")
if err != nil {
fmt.Println("error:",err)
os.Exit(1)
}
bs := make([]byte, 99999)
resp.Body.Read(bs)
fmt.Println(string(bs)) // 得到我們要的google網站的body資訊,也就是構成這個網站的html檔案
}
```
## io.Writer & io.Copy & os.Stdout
那每次我們需要透過Read函式讀取資料時,都要先建立一個byte slice嗎,會不會太麻煩。
要解決這個問題要先介紹Go裡面另外一個很強大的介面 - Writer interface。
```
type Writer interface {
Write(p []byte) (n int, err error)
}
```
我們可以看到Writer和Reader interface定義非常像,而需要注意的是Write function 和 Read function雖然接受都是byte slice,但這裡的byte slice其實已經有資料,而非Read function要傳入的是一個空的byte slice。因為這時候不是要讀取資料了,Write function是要將已經拿到的資料,轉出去給任何形式的output(圖五),例如寫到termianl、寫到某個text file等等。

(圖五,圖片來源 : [Go: The Complete Developer's Guide (Golang)](https://www.udemy.com/course/go-the-complete-developers-guide/))
接下來是io.Copy
```
func Copy(dst Writer, src Reader) (written int64, err error) {
return copyBuffer(dst, src, nil)
}
```
透過這個函式,可以我們可以透過copyBuffer這個函式,實現Readr介面的資料複製到目標的Writer介面,什麼東西實現Writer介面呢? 這裡要引入Go的標準輸出概念Stdout。
os.Stdout其型別為一個指向os.File的指針,而File在os package定義的一個型別,裡面有Write函式,所以os.Stuout可以實現Writer介面(圖六)
`func (f *File) Write(b []byte) (n int, err error)`

(圖六,圖片來源 : [Go: The Complete Developer's Guide (Golang)](https://www.udemy.com/course/go-the-complete-developers-guide/))
總和以上推論可以將程式碼修改如下,更為精簡
```go!
func main() {
resp, err := http.Get("http://google.com/")
if err != nil {
fmt.Println("error:",err)
os.Exit(1)
}
io.Copy(os.Stdout, resp.Body)
}
```
## References
1. [Go: The Complete Developer's Guide (Golang)](https://www.udemy.com/course/go-the-complete-developers-guide/)
2. [Golang net/http package](https://pkg.go.dev/net/http@go1.20.2#Get)
3. [Reader.Read([]byte) design](https://www.reddit.com/r/golang/comments/uuu52z/readerreadbyte_design/)
4. [良葛格 - io.Reader、io.Writer](https://openhome.cc/Gossip/Go/ReaderWriter.html)
5. [Golang - 認識 io package 的原理與操作](https://blog.kennycoder.io/2020/02/08/Golang-%E8%AA%8D%E8%AD%98-io-package%E7%9A%84%E5%8E%9F%E7%90%86%E8%88%87%E6%93%8D%E4%BD%9C/)
###### tags: `About Go`