Go語言接口的用法詳解
一、接口的定義和好處
我們都知道接口給類提供了一種多態(tài)的機制,什么是多態(tài),多態(tài)就是系統(tǒng)根據類型的具體實現完成不同的行為。
以下代碼簡單說明了接口的作用
package main
import (
"fmt"
"io"
"net/http"
"os"
)
// init 在main 函數之前調用
func init() {
if len(os.Args) != 2 {
fmt.Println("Usage: ./example2 <url>")
os.Exit(-1)
}
}
// main 是應用程序的入口
func main() {
// 從Web 服務器得到響應
r, err := http.Get(os.Args[1])
if err != nil {
fmt.Println(err)
return
}
// 從Body 復制到Stdout
io.Copy(os.Stdout, r.Body)
if err := r.Body.Close(); err != nil {
fmt.Println(err)
}
}①注意下 http.Get(os.Args[1]) 這里他的返回值r是一個Response對象的指針,也就是請求的結果
做過web開發(fā)的都知道,下面是源代碼
func Get(url string) (resp *Response, err error) {
return DefaultClient.Get(url)
}以下是Response的結構,這里有一個Body,是一個io.ReadCloser類型的,這是啥?往下看
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
ContentLength int64
TransferEncoding []string
Close bool
Uncompressed bool
Trailer Header
Request *Request
TLS *tls.ConnectionState
}ReadCloser是一個接口哦!Reader和Closer也同樣是接口,接口里面都是方法。
type ReadCloser interface {
Reader
Closer
}Reader接口
type Reader interface {
Read(p []byte) (n int, err error)
}Closer接口
type Closer interface {
Close() error
}②io.Copy(os.Stdout, r.Body) 這個方法,查看源碼如下
func Copy(dst Writer, src Reader) (written int64, err error) {
return copyBuffer(dst, src, nil)
}這里的輸入參數dst是一個實現了Writer接口的對象,而src則是一個實現了Reader接口的對象,由此,我們可以知道為什么io.Copy(os.Stdout, r.Body)的第二個參數可以傳r.Body了,因為①中展示了Body這個對象是實現了Reader接口的。同理os.Stdout對象這個接口值表示標準輸出設備,并且已經實現了io.Writer 接口
os.Stdout返回的是一個*File, File里面只有一個*file,而*file是實現了下面兩個接口的,下面是Go的源碼
func (f *File) Read(b []byte) (n int, err error) {
if err := f.checkValid("read"); err != nil {
return 0, err
}
n, e := f.read(b)
return n, f.wrapErr("read", e)
}
func (f *File) Write(b []byte) (n int, err error) {
if err := f.checkValid("write"); err != nil {
return 0, err
}
n, e := f.write(b)
if n < 0 {
n = 0
}
if n != len(b) {
err = io.ErrShortWrite
}
epipecheck(f, e)
if e != nil {
err = f.wrapErr("write", e)
}
return n, err
}所以說*File本身是繼承了Writer和Reader接口的類型。
綜上有了參數或者返回值是接口類型,就不用關注于具體的返回類型是什么,只要實現了的接口的方法都是可以被接受的。
二、接口值和實際對象值是怎么轉化和存儲的
我們都知道 如果一個類型實現了某個接口,那么這個類型的實際值是可以賦值給一個接口的變量的。
在C#中是這樣的,例如將一個List賦值給一個IEnumerable類型的變量
IEnumerable<int> list = new List<int>();
在Go語言中也是這樣的,請看下面的代碼
package main
import (
"fmt"
)
type eat interface{
eat()(string)
}
type Bird struct
{
Name string
}
func (bird Bird) eat()string{
return "Bird:"+bird.Name+" eat"
}
func print(e eat){
fmt.Println(e.eat())
}
// main 是應用程序的入口
func main() {
bird1:= Bird{Name:"Big"}
bird2:= new(Bird)
bird2.Name = "Small"
print(bird1)
print(bird2)
var eat1 eat
eat1 = bird1
print(eat1)
}結果
Bird:Big eat
Bird:Small eat
Bird:Big eat
這里定義了一個eat接口,有一個Bird的類型實現了該接口,print函數接受一個eat接口類型的參數,
這里可以看到前兩次直接把bird1和bird2作為參數傳入到print函數內,第二次則是聲明了一個eat接口類型的變量eat1,然后將bird1進行了賦值。換句話說接口類型變量實際承載了實際類型值。這里是如何承載的呢?
這里我們把 eat1 稱作 接口值,將bird1稱作實體類型值,eat1和bird1的關系如下:

接口值可以看成兩部分組合(都是指針)而成的。第一部分是【iTable的地址】第二部分是【實體類型值的地址】
關于interface的解析:
http://www.dhdzp.com/article/255284.htm
三、方法集的概念
簡單的講:方法集定義了接口的接受規(guī)則
舉例說明:
package main
import (
"fmt"
)
type notifier interface {
notify()
}
type user struct {
name string
email string
}
func (u user) notify() {
fmt.Printf("Sending user email to %s<%s>\n",
u.name,
u.email)
}
func sendNotification(n notifier) {
n.notify()
}
func main() {
u := user{"Bill", "bill@email.com"}
sendNotification(u)
}如上代碼,定義了一個notifier接口,有一個方法nitify()方法,定義了一個user類型的結構,實現了notify方法,接收者類型是user,即實現了notifier接口,又定義了一個sendNotification方法,接收一個實現notifier接口的類型,并調用notify方法。
func (u *user) notify() {
fmt.Printf("Sending user email to %s<%s>\n",
u.name,
u.email)
}
func main() {
u := user{"Bill", "bill@email.com"}
sendNotification(u)
}現在修正一下代碼,將接收者改為user的指針類型。此時會發(fā)現原來調用的地方會出現錯誤。
cannot use u (type user) as type notifier in argument to sendNotification:user does not implement notifier (notify method has pointer receiver)
不能將u(類型是user)作為sendNotification 的參數類型notifier:user 類型并沒有實現notifier(notify 方法使用指針接收者聲明)
為什么會出現上面的問題?要了解用指針接收者來實現接口時為什么user 類型的值無法實現該接口,需要先了解方法集。方法集定義了一組關聯到給定類型的值或者指針的方法。
定義方法時使用的接收者的類型決定了這個方法是關聯到值,還是關聯到指針,還是兩個都關聯。
以下是Go語言規(guī)范中的方法集:

上表的意思是:類型的值只能實現值接收者的接口;指向類型的指針,既可以實現值接收者的接口,也可以實現指針接收者的接口。
從接收者的角度來看一下這些規(guī)則

如果是值接收者,實體類型的值和指針都可以實現對應的接口;如果是指針接收者,那么只有類型的指針能夠實現對應的接口。
所以針對上面的問題,將傳入的u變成傳入地址就可以了(可以套用一下表格,接收者*user對應的values是*user,所以傳地址對應上面表格淺藍色部分)
func (u *user) notify() {
fmt.Printf("Sending user email to %s<%s>\n",
u.name,
u.email)
}
func main() {
u := user{"Bill", "bill@email.com"}
sendNotification(&u)
}綜上我們總結一下,也就是說如果你的方法的接受者的類型是指針類型,那么方法的實現者就只能是指向該類型的指針類型,如果方法的接收者是值類型,那么方法的實現者可以是值類型也可以是指向該類型的指針類型。
面試題一個,下面的代碼能否編譯通過?
package main
import (
"fmt"
)
type People interface {
Speak(string) string
}
type Stduent struct{}
func (stu *Stduent) Speak(think string) (talk string) {
if think == "bitch" {
talk = "You are a good boy"
} else {
talk = "hi"
}
return
}
func main() {
var peo People = Stduent{}
think := "bitch"
fmt.Println(peo.Speak(think))
}答案:不能。
分析:首先Speak的方法的接收者是*Student , 根據上面的規(guī)則,那么實現該方法的實現者只能是 *Student,但是 var peo People = Student{} 這里卻將Student作為實現者賦值給了接口,這里就會出現問題。Integer(25)是一個字面量,而字面量是一個常量,所以沒有辦法尋址。
四、多態(tài)
// Sample program to show how polymorphic behavior with interfaces.
package main
import (
"fmt"
)
type notifier interface {
notify()
}
// user defines a user in the program.
type user struct {
name string
email string
}
func (u *user) notify() {
fmt.Printf("Sending user email to %s<%s>\n",
u.name,
u.email)
}
type admin struct {
name string
email string
}
func (a *admin) notify() {
fmt.Printf("Sending admin email to %s<%s>\n",
a.name,
a.email)
}
// main is the entry point for the application.
func main() {
// Create a user value and pass it to sendNotification.
bill := user{"Bill", "bill@email.com"}
sendNotification(&bill)
// Create an admin value and pass it to sendNotification.
lisa := admin{"Lisa", "lisa@email.com"}
sendNotification(&lisa)
}
func sendNotification(n notifier) {
n.notify()
}上面的代碼很好的說明的接口的多態(tài),user和admin都實現了notify方法,既實現了的notifier接口,sendNotification函數接收一個實現notifier接口的實例,從而user和admin都可以當作參數使用sendNotification函數,而sendNotification里面的notify方法執(zhí)行根據的是具體傳入的實例中實現的方法。
到此這篇關于Go語言接口的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
解決golang處理http response碰到的問題和需要注意的點
這篇文章主要介紹了解決golang處理http response碰到的問題和需要注意的點,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12

