網(wǎng)站首頁 編程語言 正文
一、接口的定義和好處
我們都知道接口給類提供了一種多態(tài)的機(jī)制,什么是多態(tài),多態(tài)就是系統(tǒng)根據(jù)類型的具體實(shí)現(xiàn)完成不同的行為。
以下代碼簡單說明了接口的作用
package main import ( "fmt" "io" "net/http" "os" ) // init 在main 函數(shù)之前調(diào)用 func init() { if len(os.Args) != 2 { fmt.Println("Usage: ./example2 <url>") os.Exit(-1) } } // main 是應(yīng)用程序的入口 func main() { // 從Web 服務(wù)器得到響應(yīng) r, err := http.Get(os.Args[1]) if err != nil { fmt.Println(err) return } // 從Body 復(fù)制到Stdout io.Copy(os.Stdout, r.Body) if err := r.Body.Close(); err != nil { fmt.Println(err) } }
①注意下 http.Get(os.Args[1]) 這里他的返回值r是一個(gè)Response對象的指針,也就是請求的結(jié)果
做過web開發(fā)的都知道,下面是源代碼
func Get(url string) (resp *Response, err error) { return DefaultClient.Get(url) }
以下是Response的結(jié)構(gòu),這里有一個(gè)Body,是一個(gè)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是一個(gè)接口哦!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) 這個(gè)方法,查看源碼如下
func Copy(dst Writer, src Reader) (written int64, err error) { return copyBuffer(dst, src, nil) }
這里的輸入?yún)?shù)dst是一個(gè)實(shí)現(xiàn)了Writer接口的對象,而src則是一個(gè)實(shí)現(xiàn)了Reader接口的對象,由此,我們可以知道為什么io.Copy(os.Stdout, r.Body)的第二個(gè)參數(shù)可以傳r.Body了,因?yàn)棰僦姓故玖薆ody這個(gè)對象是實(shí)現(xiàn)了Reader接口的。同理os.Stdout對象這個(gè)接口值表示標(biāo)準(zhǔn)輸出設(shè)備,并且已經(jīng)實(shí)現(xiàn)了io.Writer 接口
os.Stdout返回的是一個(gè)*File, File里面只有一個(gè)*file,而*file是實(shí)現(xiàn)了下面兩個(gè)接口的,下面是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接口的類型。
綜上有了參數(shù)或者返回值是接口類型,就不用關(guān)注于具體的返回類型是什么,只要實(shí)現(xiàn)了的接口的方法都是可以被接受的。
二、接口值和實(shí)際對象值是怎么轉(zhuǎn)化和存儲的
我們都知道 如果一個(gè)類型實(shí)現(xiàn)了某個(gè)接口,那么這個(gè)類型的實(shí)際值是可以賦值給一個(gè)接口的變量的。
在C#中是這樣的,例如將一個(gè)List賦值給一個(gè)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 是應(yīng)用程序的入口 func main() { bird1:= Bird{Name:"Big"} bird2:= new(Bird) bird2.Name = "Small" print(bird1) print(bird2) var eat1 eat eat1 = bird1 print(eat1) }
結(jié)果
Bird:Big eat
Bird:Small eat
Bird:Big eat
這里定義了一個(gè)eat接口,有一個(gè)Bird的類型實(shí)現(xiàn)了該接口,print函數(shù)接受一個(gè)eat接口類型的參數(shù),
這里可以看到前兩次直接把bird1和bird2作為參數(shù)傳入到print函數(shù)內(nèi),第二次則是聲明了一個(gè)eat接口類型的變量eat1,然后將bird1進(jìn)行了賦值。換句話說接口類型變量實(shí)際承載了實(shí)際類型值。這里是如何承載的呢?
這里我們把 eat1 稱作 接口值,將bird1稱作實(shí)體類型值,eat1和bird1的關(guān)系如下:
接口值可以看成兩部分組合(都是指針)而成的。第一部分是【iTable的地址】第二部分是【實(shí)體類型值的地址】
關(guān)于interface的解析:
https://www.jb51.net/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) }
如上代碼,定義了一個(gè)notifier接口,有一個(gè)方法nitify()方法,定義了一個(gè)user類型的結(jié)構(gòu),實(shí)現(xiàn)了notify方法,接收者類型是user,即實(shí)現(xiàn)了notifier接口,又定義了一個(gè)sendNotification方法,接收一個(gè)實(shí)現(xiàn)notifier接口的類型,并調(diào)用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) }
現(xiàn)在修正一下代碼,將接收者改為user的指針類型。此時(shí)會發(fā)現(xiàn)原來調(diào)用的地方會出現(xiàn)錯(cuò)誤。
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 的參數(shù)類型notifier:user 類型并沒有實(shí)現(xiàn)notifier(notify 方法使用指針接收者聲明)
為什么會出現(xiàn)上面的問題?要了解用指針接收者來實(shí)現(xiàn)接口時(shí)為什么user 類型的值無法實(shí)現(xiàn)該接口,需要先了解方法集。方法集定義了一組關(guān)聯(lián)到給定類型的值或者指針的方法。
定義方法時(shí)使用的接收者的類型決定了這個(gè)方法是關(guān)聯(lián)到值,還是關(guān)聯(lián)到指針,還是兩個(gè)都關(guān)聯(lián)。
以下是Go語言規(guī)范中的方法集:
上表的意思是:類型的值只能實(shí)現(xiàn)值接收者的接口;指向類型的指針,既可以實(shí)現(xiàn)值接收者的接口,也可以實(shí)現(xiàn)指針接收者的接口。
從接收者的角度來看一下這些規(guī)則
如果是值接收者,實(shí)體類型的值和指針都可以實(shí)現(xiàn)對應(yīng)的接口;如果是指針接收者,那么只有類型的指針能夠?qū)崿F(xiàn)對應(yīng)的接口。
所以針對上面的問題,將傳入的u變成傳入地址就可以了(可以套用一下表格,接收者*user對應(yīng)的values是*user,所以傳地址對應(yīng)上面表格淺藍(lán)色部分)
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) }
綜上我們總結(jié)一下,也就是說如果你的方法的接受者的類型是指針類型,那么方法的實(shí)現(xiàn)者就只能是指向該類型的指針類型,如果方法的接收者是值類型,那么方法的實(shí)現(xiàn)者可以是值類型也可以是指向該類型的指針類型。
面試題一個(gè),下面的代碼能否編譯通過?
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 , 根據(jù)上面的規(guī)則,那么實(shí)現(xiàn)該方法的實(shí)現(xiàn)者只能是 *Student,但是 var peo People = Student{} 這里卻將Student作為實(shí)現(xiàn)者賦值給了接口,這里就會出現(xiàn)問題。Integer(25)是一個(gè)字面量,而字面量是一個(gè)常量,所以沒有辦法尋址。
四、多態(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都實(shí)現(xiàn)了notify方法,既實(shí)現(xiàn)了的notifier接口,sendNotification函數(shù)接收一個(gè)實(shí)現(xiàn)notifier接口的實(shí)例,從而user和admin都可以當(dāng)作參數(shù)使用sendNotification函數(shù),而sendNotification里面的notify方法執(zhí)行根據(jù)的是具體傳入的實(shí)例中實(shí)現(xiàn)的方法。
原文鏈接:https://www.cnblogs.com/dcz2015/p/10103353.html
相關(guān)推薦
- 2023-10-17 css標(biāo)簽畫圓形進(jìn)度條
- 2022-12-21 Android?ChipGroup收起折疊效果實(shí)現(xiàn)詳解_Android
- 2022-04-28 淺析python中特殊文件和特殊函數(shù)_python
- 2023-01-17 SqlServer事務(wù)語法及使用方法詳解_MsSql
- 2022-05-25 C語言三個(gè)函數(shù)的模擬實(shí)現(xiàn)詳解_C 語言
- 2022-12-11 Golang?內(nèi)存模型The?Go?Memory?Model_Golang
- 2022-12-14 Android?Studio?gradle配置packagingOptions打包so庫重復(fù)_And
- 2023-05-24 Python使用Pandas處理測試數(shù)據(jù)的方法_python
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支