剛開始做Go語言開發的過程中,經常會有一個疑問,使用接口封裝的方法和不使用接口封裝的方法,在本質上是沒有區別的,但是在讀了io標準庫之後,對兩者的使用有了進一步的理解,在這裏總結一下。
1.什麼是方法
在 go語言中可以通過下面的形式定義方法
func (A type) function_name(args) {}
其中A爲一種定義好的類型,args爲傳入的參數,調用方法時,必須使用type.function_name
的形式調用,type是定義好的類型,舉個例子:
package main
import (
"fmt"
)
// 定義結構體類型User
type User struct {
Name string
Age int
}
// User類型的方法,用來輸出用戶名和密碼
func (u *User) GetUserInfo() {
fmt.Println("Name:", u.Name, "Age:", u.Age)
return
}
func main() {
var u User
u.Name = "random"
u.Age = 18
// 調用方法
u.GetUserInfo()
}
Output:
$ go run main.go
Name: random Age: 18
2.如何使用接口封裝方法
Go語言通過接口實現了鴨子類型(duck-typing):“當看到一直鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那麼這隻鳥就可以被稱爲鴨子”。我們並不關心對象是什麼類型,到底是不是鴨子,只關心行爲,這也就是其他編程語言中的多態。
下面舉一個例子:
package main
import (
"fmt"
)
// 對方法進行封裝
type IUser interface {
GetUserInfo()
}
// 定義結構體類型User
type User struct {
Name string
Age int
}
// User類型的方法,用來輸出用戶名和密碼
func (u *User) GetUserInfo() {
fmt.Println("Name:", u.Name, "Age:", u.Age)
return
}
func main() {
var u User
u.Name = "random"
u.Age = 18
var user IUser = &u
user.GetUserInfo()
}
Output:
$ go run main.go
Name: random Age: 18
與方法相比這種調用方式顯得複雜,那麼什麼時候使用接口封裝方法比較好呢?
3.什麼時候使用接口
這裏以io標準庫爲例,在io包中最重要的是兩個接口:Reader和Writer接口,首先來介紹這兩個接口。
type Reader interface {
Read(p []byte) (n int, err error)
}
Reader 接口包裝了基本的 Read 方法。
Read 將 len( p) 個字節讀取到 p 中。它返回讀取的字節數 n(0 <= n <= len§)以及任何遇到的錯誤。即使 Read 返回的 n < len§,它也會在調用過程中使用 p的全部作爲暫存空間。若一些數據可用但不到 len( p) 個字節,Read 會照例返回可用的東西,而不是等待更多。
當 Read 在成功讀取 n > 0 個字節後遇到一個錯誤或 EOF 情況,它就會返回讀取的字節數。它會從相同的調用中返回(非nil的)錯誤或從隨後的調用中返回錯誤(和 n == 0)。這種一般情況的一個例子就是 Reader 在輸入流結束時會返回一個非零的字節數,可能的返回不是 err == EOF 就是 err == nil。無論如何,下一個 Read 都應當返回 0, EOF。
調用者應當總在考慮到錯誤 err 前處理 n > 0 的字節。這樣做可以在讀取一些字節,以及允許的 EOF 行爲後正確地處理I/O錯誤。
Read 的實現會阻止返回零字節的計數和一個 nil 錯誤,調用者應將這種情況視作空操作。
io庫中有一個函數: func ReadFull(r Reader, buf []byte) (n int, err error)
,這個函數可以把對象 r 中的數據讀出來,然後存入一個緩衝區 buf 中,以便其它代碼可以處理 buf 中的數據,ReadFull 可以讀取任何對象的數據,但是有個前提,就是這個對象必須符合 Reader 的標準
。
讀到這裏大家應該能看出接口封裝的作用了,它可以實現在不知道對象類型的情況下可以對對象進行一些操作。
// 定義一個 str 類型(以 string 爲基類型)
type str string
// 實現 str 類型的 Read 方法
func (s str) Read(p []byte) (n int, err error) {
i, ls, lp := 0, len(s), len(p)
for ; i < ls && i < lp; i++ {
// 將小寫字母轉換爲大寫字母,然後寫入 p 中
if s[i] >= 'a' && s[i] <= 'z' {
p[i] = s[i] + 'A' - 'a'
} else {
p[i] = s[i]
}
}
// 根據讀取的字節數設置返回值
switch i {
case lp:
return i, nil
case ls:
return i, io.EOF
default:
return i, errors.New("read fail")
}
}
func main() {
s := str("Hello World!") // 創建 str 對象 us
buf := make([]byte, 32) // 創建緩衝區 buf
n, err := io.ReadFull(s, buf) // 將 s 中的數據讀取到 buf 中
if err != nil && err != io.ErrUnexpectedEOF {
return
}
fmt.Printf("%s\nlength:%d", buf, n) // 顯示 buf 中的內容
}
Output:
$ go run main.go
HELLO WORLD!
length:12