Golang 學習筆記七 接口

一肢础、概念

《快學 Go 語言》第 9 課 —— 接口

1.接口定義
Go 語言的接口類型非常特別,它的作用和 Java 語言的接口一樣歌殃,但是在形式上有很大的差別乔妈。Java 語言需要在類的定義上顯式實現(xiàn)了某些接口蝙云,才可以說這個類具備了接口定義的能力氓皱。但是 Go 語言的接口是隱式的,只要結(jié)構(gòu)體上定義的方法在形式上(名稱勃刨、參數(shù)和返回值)和接口定義的一樣波材,那么這個結(jié)構(gòu)體就自動實現(xiàn)了這個接口,我們就可以使用這個接口變量來指向這個結(jié)構(gòu)體對象身隐。下面我們看個例子

package main

import "fmt"

// 可以聞
type Smellable interface {
  smell()
}

// 可以吃
type Eatable interface {
  eat()
}

// 蘋果既可能聞又能吃
type Apple struct {}

func (a Apple) smell() {
  fmt.Println("apple can smell")
}

func (a Apple) eat() {
  fmt.Println("apple can eat")
}

// 花只可以聞
type Flower struct {}

func (f Flower) smell() {
  fmt.Println("flower can smell")
}

func main() {
  var s1 Smellable
  var s2 Eatable
  var apple = Apple{}
  var flower = Flower{}
  s1 = apple
  s1.smell()
  s1 = flower
  s1.smell()
  s2 = apple
  s2.eat()
}

--------------------
apple can smell
flower can smell
apple can eat

上面的代碼定義了兩種接口廷区,Apple 結(jié)構(gòu)體同時實現(xiàn)了這兩個接口,而 Flower 結(jié)構(gòu)體只實現(xiàn)了 Smellable 接口贾铝。我們并沒有使用類似于 Java 語言的 implements 關(guān)鍵字隙轻,結(jié)構(gòu)體和接口就自動產(chǎn)生了關(guān)聯(lián)埠帕。

2.空接口
如果一個接口里面沒有定義任何方法,那么它就是空接口玖绿,任意結(jié)構(gòu)體都隱式地實現(xiàn)了空接口敛瓷。

Go 語言為了避免用戶重復定義很多空接口,它自己內(nèi)置了一個斑匪,這個空接口的名字特別奇怪呐籽,叫 interface{} ,初學者會非常不習慣蚀瘸。之所以這個類型名帶上了大括號狡蝶,那是在告訴用戶括號里什么也沒有。我始終認為這種名字很古怪贮勃,它讓代碼看起來有點丑陋贪惹。

空接口里面沒有方法,所以它也不具有任何能力衙猪,其作用相當于 Java 的 Object 類型馍乙,可以容納任意對象,它是一個萬能容器垫释。比如一個字典的 key 是字符串丝格,但是希望 value 可以容納任意類型的對象,類似于 Java 語言的 Map 類型棵譬,這時候就可以使用空接口類型 interface{}显蝌。

package main

import "fmt"

func main() {
    // 連續(xù)兩個大括號,是不是看起來很別扭
    var user = map[string]interface{}{
        "age": 30,
        "address": "Beijing Tongzhou",
        "married": true,
    }
    fmt.Println(user)
    // 類型轉(zhuǎn)換語法來了
    var age = user["age"].(int)
    var address = user["address"].(string)
    var married = user["married"].(bool)
    fmt.Println(age, address, married)
}

-------------
map[age:30 address:Beijing Tongzhou married:true]
30 Beijing Tongzhou true

代碼中 user 字典變量的類型是 map[string]interface{}订咸,從這個字典中直接讀取得到的 value 類型是 interface{}曼尊,需要通過類型轉(zhuǎn)換才能得到期望的變量。

3.用接口來模擬多態(tài)

package main

import "fmt"

type Fruitable interface {
    eat()
}

type Fruit struct {
    Name string  // 屬性變量
    Fruitable  // 匿名內(nèi)嵌接口變量
}

func (f Fruit) want() {
    fmt.Printf("I like ")
    f.eat() // 外結(jié)構(gòu)體會自動繼承匿名內(nèi)嵌變量的方法
}

type Apple struct {}

func (a Apple) eat() {
    fmt.Println("eating apple")
}

type Banana struct {}

func (b Banana) eat() {
    fmt.Println("eating banana")
}

func main() {
    var f1 = Fruit{"Apple", Apple{}}
    var f2 = Fruit{"Banana", Banana{}}
    f1.want()
    f2.want()
}

---------
I like eating apple
I like eating banana

使用這種方式模擬多態(tài)本質(zhì)上是通過組合屬性變量(Name)和接口變量(Fruitable)來做到的脏嚷,屬性變量是對象的數(shù)據(jù)骆撇,而接口變量是對象的功能,將它們組合到一塊就形成了一個完整的多態(tài)性的結(jié)構(gòu)體父叙。
《GoInAction》第118頁也提供了一個例子:

// Sample program to show how polymorphic behavior with interfaces.
package main

import (
    "fmt"
)

// notifier is an interface that defines notification
// type behavior.
type notifier interface {
    notify()
}

// user defines a user in the program.
type user struct {
    name  string
    email string
}

// notify implements the notifier interface with a pointer receiver.
func (u *user) notify() {
    fmt.Printf("Sending user email to %s<%s>\n",
        u.name,
        u.email)
}

// admin defines a admin in the program.
type admin struct {
    name  string
    email string
}

// notify implements the notifier interface with a pointer receiver.
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)
}

// sendNotification accepts values that implement the notifier
// interface and sends notifications.
func sendNotification(n notifier) {
    n.notify()
}

在第 53 行中神郊,我們再次聲明了多態(tài)函數(shù) sendNotification,這個函數(shù)接受一個實現(xiàn)了notifier 接口的值作為參數(shù)趾唱。既然任意一個實體類型都能實現(xiàn)該接口涌乳,那么這個函數(shù)可以針對任意實體類型的值來執(zhí)行 notifier 方法。因此甜癞,這個函數(shù)就能提供多態(tài)的行為夕晓。

4.接口的組合繼承
接口的定義也支持組合繼承,比如我們可以將兩個接口定義合并為一個接口如下

type Smellable interface {
  smell()
}

type Eatable interface {
  eat()
}

type Fruitable interface {
  Smellable
  Eatable
}

這時 Fruitable 接口就自動包含了 smell() 和 eat() 兩個方法悠咱,它和下面的定義是等價的蒸辆。

type Fruitable interface {
  smell()
  eat()
}

5.接口變量的賦值
變量賦值本質(zhì)上是一次內(nèi)存淺拷貝,切片的賦值是拷貝了切片頭柒室,字符串的賦值是拷貝了字符串的頭部逗宜,而數(shù)組的賦值呢是直接拷貝整個數(shù)組雄右。接口變量的賦值會不會不一樣呢擂仍?接下來我們做一個實驗

package main

import "fmt"

type Rect struct {
    Width int
    Height int
}

func main() {
    var a interface {}
    var r = Rect{50, 50}
    a = r

    var rx = a.(Rect)
    r.Width = 100
    r.Height = 100
    fmt.Println(rx)
}

------
{50 50}

6.嵌入類型
《GoInAction》也提供了例子

// Sample program to show how to embed a type into another type and
// the relationship between the inner and outer type.
package main

import (
    "fmt"
)

// user defines a user in the program.
type user struct {
    name  string
    email string
}

// notify implements a method that can be called via
// a value of type user.
func (u *user) notify() {
    fmt.Printf("Sending user email to %s<%s>\n",
        u.name,
        u.email)
}

// admin represents an admin user with privileges.
type admin struct {
    user  // Embedded Type
    level string
}

// main is the entry point for the application.
func main() {
    // Create an admin user.
    ad := admin{
        user: user{
            name:  "john smith",
            email: "john@yahoo.com",
        },
        level: "super",
    }

    // We can access the inner type's method directly.
    ad.user.notify()

    // The inner type's method is promoted.
    ad.notify()
}

這展示了內(nèi)部類型是如何存在于外部類型內(nèi),并且總是可訪問的熬甚。不過乡括,借助內(nèi)部類型提升诲泌,notify 方法也可以直接通過 ad 變量來訪問

再改造一下:

// notifier is an interface that defined notification
// type behavior.
type notifier interface {
    notify()
}
// sendNotification accepts values that implement the notifier
// interface and sends notifications.
func sendNotification(n notifier) {
    n.notify()
}
// main is the entry point for the application.
func main() {
    // Create an admin user.
    ad := admin{
        user: user{
            name:  "john smith",
            email: "john@yahoo.com",
        },
        level: "super",
    }

    // Send the admin user a notification.
    // The embedded inner type's implementation of the
    // interface is "promoted" to the outer type.
    sendNotification(&ad)
}

在代碼清單 5-58 的第 37 行哀蘑,我們創(chuàng)建了一個名為 ad 的變量绘迁,其類型是外部類型 admin缀台。這個類型內(nèi)部嵌入了 user 類型膛腐。之后第 48 行依疼,我們將這個外部類型變量的地址傳給 sendNotification 函數(shù)。編譯器認為這個指針實現(xiàn)了 notifier 接口棍丐,并接受了這個值的傳遞歌逢。不過如果看一下整個示例程序秘案,就會發(fā)現(xiàn) admin 類型并沒有實現(xiàn)這個接口阱高。由于內(nèi)部類型的提升赤惊,內(nèi)部類型實現(xiàn)的接口會自動提升到外部類型未舟。這意味著由于內(nèi)部類型的實現(xiàn)员串,外部類型也同樣實現(xiàn)了這個接口昵济。

如果外部類型并不需要使用內(nèi)部類型的實現(xiàn)访忿,而想使用自己的一套實現(xiàn)海铆,該怎么辦卧斟?

// notify implements a method that can be called via
// a value of type Admin.
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 an admin user.
    ad := admin{
        user: user{
            name:  "john smith",
            email: "john@yahoo.com",
        },
        level: "super",
    }

    // Send the admin user a notification.
    // The embedded inner type's implementation of the
    // interface is NOT "promoted" to the outer type.
    sendNotification(&ad)

    // We can access the inner type's method directly.
    ad.user.notify()

    // The inner type's method is NOT promoted.
    ad.notify()
}

---------------------------------------------
Sending admin email to john smith<john@yahoo.com>
Sending user email to john smith<john@yahoo.com>
Sending admin email to john smith<john@yahoo.com>

這表明,如果外部類型實現(xiàn)了 notify 方法板乙,內(nèi)部類型的實現(xiàn)就不會被提升募逞。不過內(nèi)部類型的值一直存在放接,因此還可以通過直接訪問內(nèi)部類型的值纠脾,來調(diào)用沒有被提升的內(nèi)部類型實現(xiàn)的方法捧韵。

關(guān)于嵌套結(jié)構(gòu)體的應用再来,可以在Golang sort自定義排序中看到

二芒篷、值接收和引用接收

Golang 學習筆記六 函數(shù)function和方法method的區(qū)別講方法時,有個例子贸呢,當通過值或指針調(diào)用方法時,go編譯器會自動幫我們處理方法接收者的不一致结执。

type user struct{
    name string
    email string
}

func (u user) printName(){
    fmt.Printf("name: %s\n", u.name)
}

func (u *user) printEmail(){
    fmt.Printf("email: %s\n", u.email)
}

func main() {
    bill := user{"bill","bill@gmail.com"}
    lisa := &user{"lisa","lisa@gmail.com"};

    bill.printName()
    lisa.printName()

    bill.printEmail()
    lisa.printEmail()
}

正常打永小:

name: bill
name: lisa
email: bill@gmail.com
email: lisa@gmail.com

但是悟泵,如果通過接口類型的值調(diào)用方法,規(guī)則有很大不同:
在上面代碼中加上兩個接口

type printNamer interface{
    printName()
}

type printEmailer interface{
    printEmail()
}

func sendPrintName(n printNamer) {
    n.printName()
}

func sendPrintEmail(n printEmailer){
    n.printEmail()
}

func main() {
        ...
    sendPrintName(bill)
    sendPrintName(lisa)

    sendPrintEmail(bill)
    sendPrintEmail(lisa)

這里sendPrintEmail(bill)編譯不通過衡招,提示:cannot use bill (type user) as type printEmailer in argument to sendPrintEmail:user does not implement printEmailer (printEmail method has pointer receiver)

觀察一下區(qū)別州刽,bill是一個值奶栖,printEmail接收者是個指針冻晤,失敗了。但是另外一個不一致的卻能通過绣硝,那就是lisa是個指針,但是printName的接收者要求是值挠铲,為啥就能通過呢痰洒。

在《Go in Action》第118頁描述了方法集的規(guī)則:
使用指針作為接收者聲明的方法脯宿,只能在接口類型的值是一個指針的時候被調(diào)用嗡靡。使用值作為接收者聲明的方法,在接口類型的值為值或者指針時,都可以被調(diào)用。

為什么會有這種限制菜谣?事實上,編譯器并不是總能自動獲得一個值的地址,如代碼清單 5-46 所示暮蹂。

代碼清單 5-46 listing46.go
01 // 這個示例程序展示不是總能
02 // 獲取值的地址
03 package main
04
05 import "fmt"
06
07 // duration 是一個基于 int 類型的類型
08 type duration int
09
10 // 使用更可讀的方式格式化 duration 值
11 func (d *duration) pretty() string {
12  return fmt.Sprintf("Duration: %d", *d)
13 }
14
15 // main 是應用程序的入口
16 func main() {
17  duration(42).pretty()
18
19  // ./listing46.go:17: 不能通過指針調(diào)用 duration(42)的方法
20  // ./listing46.go:17: 不能獲取 duration(42)的地址
21 }

這里編譯通過滩届,運行也會報錯券犁。我們改成變量調(diào)用就可以了:

    dd := duration(42)
    dd.pretty()
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末稚新,一起剝皮案震驚了整個濱河市褂删,隨后出現(xiàn)的幾起案子屯阀,更是在濱河造成了極大的恐慌,老刑警劉巖盖袭,帶你破解...
    沈念sama閱讀 216,843評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異醇蝴,居然都是意外死亡悠栓,警方通過查閱死者的電腦和手機笙瑟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來错洁,“玉大人屯碴,你說我怎么就攤上這事隔崎【糇洌” “怎么了?”我有些...
    開封第一講書人閱讀 163,187評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長婉支,這世上最難降的妖魔是什么向挖? 我笑而不...
    開封第一講書人閱讀 58,264評論 1 292
  • 正文 為了忘掉前任跟畅,我火速辦了婚禮,結(jié)果婚禮上虱痕,老公的妹妹穿的比我還像新娘。我一直安慰自己捎迫,他們只是感情好蛔翅,可當我...
    茶點故事閱讀 67,289評論 6 390
  • 文/花漫 我一把揭開白布爵政。 她就那樣靜靜地躺著掺出,像睡著了一般荠诬。 火紅的嫁衣襯著肌膚如雪聂抢。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,231評論 1 299
  • 那天台汇,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛屈留,可吹牛的內(nèi)容都是我干的沫勿。 我是一名探鬼主播,決...
    沈念sama閱讀 40,116評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼刀诬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起又碌,我...
    開封第一講書人閱讀 38,945評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后剖毯,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體胶滋,經(jīng)...
    沈念sama閱讀 45,367評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,581評論 2 333
  • 正文 我和宋清朗相戀三年涯穷,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片起意。...
    茶點故事閱讀 39,754評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤斑胜,帶...
    沈念sama閱讀 35,458評論 5 344
  • 正文 年R本政府宣布么夫,位于F島的核電站档痪,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏锯仪。R本人自食惡果不足惜救鲤,卻給世界環(huán)境...
    茶點故事閱讀 41,068評論 3 327
  • 文/蒙蒙 一本缠、第九天 我趴在偏房一處隱蔽的房頂上張望斥扛。 院中可真熱鬧,春花似錦、人聲如沸稀颁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽棱烂。三九已至,卻和暖如春阶女,著一層夾襖步出監(jiān)牢的瞬間颊糜,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評論 1 269
  • 我被黑心中介騙來泰國打工秃踩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留衬鱼,地道東北人。 一個月前我還...
    沈念sama閱讀 47,797評論 2 369
  • 正文 我出身青樓憔杨,卻偏偏與公主長得像鸟赫,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子消别,可洞房花燭夜當晚...
    茶點故事閱讀 44,654評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 環(huán)境搭建 Golang在Mac OS上的環(huán)境配置 使用Visual Studio Code輔助Go源碼編寫 VS ...
    隕石墜滅閱讀 5,777評論 0 5
  • 這是16年5月份編輯的一份比較雜亂適合自己觀看的學習記錄文檔惯疙,今天18年5月份再次想寫文章,發(fā)現(xiàn)簡書還為我保存起的...
    Jenaral閱讀 2,752評論 2 9
  • 1.安裝 https://studygolang.com/dl 2.使用vscode編輯器安裝go插件 3.go語...
    go含羞草閱讀 1,553評論 0 6
  • 女生宿舍由兩個阿姨管妖啥,一個常駐的阿姨叫秀姨霉颠。秀姨在學校里做了有些年份了,她的老頭子在對面男生宿舍做宿管荆虱;聽人說她和...
    張俞辛閱讀 1,415評論 0 1
  • 愛本是泡沫,怪我沒有看破菜枷,才如此難過苍糠。 ——鄧紫棋 有時候看透和明白是兩碼事。有些人說不清楚哪里好啤誊,但就是誰都替代...
    史洪燕shy閱讀 649評論 8 10