Golang struct涮拗、interface 組合嵌入類型詳解

概述
在 Go 語言中,如果一個結(jié)構(gòu)體和一個嵌入字段同時實(shí)現(xiàn)了相同的接口會發(fā)生什么呢?我們猜一下三热,可能有兩個問題:

  • 編譯器會因為我們同時有兩個接口實(shí)現(xiàn)而報錯嗎鼓择?
  • 如果編譯器接受這樣的定義,那么當(dāng)接口調(diào)用時編譯器要怎么確定該使用哪個實(shí)現(xiàn)就漾?

實(shí)現(xiàn)接口
當(dāng)涉及到我們該怎么讓我們的類型實(shí)現(xiàn)接口時呐能,Go 語言是特別的一個。Go 語言不需要我們顯式的實(shí)現(xiàn)類型的接口抑堡。如果一個接口里的所有方法都被我們的類型實(shí)現(xiàn)了摆出,那么我們就說該類型實(shí)現(xiàn)了該接口。

package main

import (
    "log"
)

type User struct {
    Name  string
    Email string
}

func (u *User) Notify() error {
    log.Printf("User: Sending User Email To %s<%s>\n",
        u.Name,
        u.Email)

    return nil
}

type Notifier interface {
    Notify() error
}

func SendNotification(notify Notifier) error {
    return notify.Notify()
}

func main() {
    user := User{
        Name:  "AriesDevil",
        Email: "ariesdevil@xxoo.com",
    }

    SendNotification(user)
}

實(shí)例源碼:http://play.golang.org/p/KG8-Qb7gqM
以上代碼輸出:
prog.go:34: cannot use user (type User) as type Notifier in argument to SendNotification: User does not implement Notifier (Notify method has pointer receiver)

為什么編譯器不考慮我們的值是實(shí)現(xiàn)該接口的類型首妖?接口的調(diào)用規(guī)則是建立在這些方法的接受者和接口如何被調(diào)用的基礎(chǔ)上偎漫。下面的是語言規(guī)范里定義的規(guī)則,這些規(guī)則用來說明是否我們一個類型的值或者指針實(shí)現(xiàn)了該接口:

  • 類型 *T 的可調(diào)用方法集包含接受者為 *T 或 T 的所有方法集
    這條規(guī)則說的是如果我們用來調(diào)用特定接口方法的接口變量是一個指針類型有缆,那么方法的接受者可以是值類型也可以是指針類型象踊。顯然我們的例子不符合該規(guī)則,因為我們傳入 SendNotification 函數(shù)的接口變量是一個值類型棚壁。
  • 類型 T 的可調(diào)用方法集包含接受者為 T 的所有方法
    這條規(guī)則說的是如果我們用來調(diào)用特定接口方法的接口變量是一個值類型杯矩,那么方法的接受者必須也是值類型該方法才可以被調(diào)用。顯然我們的例子也不符合這條規(guī)則袖外,因為我們 Notify 方法的接受者是一個指針類型史隆。
    語言規(guī)范里只有這兩條規(guī)則,我通過這兩條規(guī)則得出了符合我們例子的規(guī)則:
  • 類型 T 的可調(diào)用方法集不包含接受者為 *T 的方法
    我們碰巧趕上了我推斷出的這條規(guī)則在刺,所以編譯器會報錯逆害。Notify 方法使用指針類型作為接受者而我們卻通過值類型來調(diào)用該方法头镊。解決辦法也很簡單蚣驼,我們只需要傳入 User 值的地址到 SendNotification 函數(shù)就好了:
package main

import (
    "log"
)

type User struct {
    Name  string
    Email string
}

func (u *User) Notify() error {
    log.Printf("User: Sending User Email To %s<%s>\n",
        u.Name,
        u.Email)

    return nil
}

type Notifier interface {
    Notify() error
}

func SendNotification(notify Notifier) error {
    return notify.Notify()
}

func main() {
    user := &User{
        Name:  "AriesDevil",
        Email: "ariesdevil@xxoo.com",
    }

    SendNotification(user)
}

嵌入類型
結(jié)構(gòu)體類型可以包含匿名或者嵌入字段。也叫做嵌入一個類型相艇。當(dāng)我們嵌入一個類型到結(jié)構(gòu)體中時颖杏,該類型的名字充當(dāng)了嵌入字段的字段名。

package main

import (
    "log"
)

type User struct {
    Name  string
    Email string
}

type Admin struct {
    User
    Level string
}

func (u *User) Notify() error {
    log.Printf("User: Sending User Email To %s<%s>\n",
        u.Name,
        u.Email)

    return nil
}

type Notifier interface {
    Notify() error
}

func SendNotification(notify Notifier) error {
    return notify.Notify()
}

func main() {
    admin := &Admin{
        User: User{
            Name:  "AriesDevil",
            Email: "ariesdevil@xxoo.com",
        },
        Level: "super",
    }

    SendNotification(admin)
}

詳細(xì)代碼:http://play.golang.org/p/ivzzzk78TC
事實(shí)證明坛芽,我們可以 Admin 類型的一個指針來調(diào)用 SendNotification 函數(shù)×舸ⅲ現(xiàn)在 Admin 類型也通過來自嵌入的 User 類型的方法提升實(shí)現(xiàn)了該接口。

如果 Admin 類型包含了 User 類型的字段和方法咙轩,那么它們在結(jié)構(gòu)體中的關(guān)系是怎么樣的呢获讳?

當(dāng)我們嵌入一個類型,這個類型的方法就變成了外部類型的方法活喊,但是當(dāng)它被調(diào)用時丐膝,方法的接受者是內(nèi)部類型(嵌入類型),而非外部類型漆撞∶墩常— Effective Go

因此嵌入類型的名字充當(dāng)著字段名柒巫,同時嵌入類型作為內(nèi)部類型存在响疚,我們可以使用下面的調(diào)用方法:

admin.User.Notify()
// OutputUser: Sending User Email To AriesDevil<ariesdevil@xxoo.com>

這兒我們通過類型名稱來訪問內(nèi)部類型的字段和方法宵蛀。然而西乖,這些字段和方法也同樣被提升到了外部類型:

admin.Notify()
// OutputUser: Sending User Email To AriesDevil<ariesdevil@xxoo.com>

所以通過外部類型來調(diào)用 Notify 方法敷燎,本質(zhì)上是內(nèi)部類型的方法谣殊。

下面是 Go 語言中內(nèi)部類型方法集提升的規(guī)則:
給定一個結(jié)構(gòu)體類型 S 和一個命名為 T 的類型凛俱,方法提升像下面規(guī)定的這樣被包含在結(jié)構(gòu)體方法集中:

  • 如果 S 包含一個匿名字段 T紊馏,S 和 *S 的方法集都包含接受者為 T 的方法提升。

這條規(guī)則說的是當(dāng)我們嵌入一個類型最冰,嵌入類型的接受者為值類型的方法將被提升瘦棋,可以被外部類型的值和指針調(diào)用。

  • 對于 *S 類型的方法集包含接受者為 *T 的方法提升

這條規(guī)則說的是當(dāng)我們嵌入一個類型暖哨,可以被外部類型的指針調(diào)用的方法集只有嵌入類型的接受者為指針類型的方法集赌朋,也就是說,當(dāng)外部類型使用指針調(diào)用內(nèi)部類型的方法時篇裁,只有接受者為指針類型的內(nèi)部類型方法集將被提升沛慢。

  • 如果 S 包含一個匿名字段 *T,S 和 *S 的方法集都包含接受者為 T 或者 *T 的方法提升

這條規(guī)則說的是當(dāng)我們嵌入一個類型的指針达布,嵌入類型的接受者為值類型或指針類型的方法將被提升团甲,可以被外部類型的值或者指針調(diào)用。
這就是語言規(guī)范里方法提升中僅有的三條規(guī)則黍聂,我根據(jù)這個推導(dǎo)出一條規(guī)則:
如果 S 包含一個匿名字段 T躺苦,S 的方法集不包含接受者為 *T 的方法提升。
這條規(guī)則說的是當(dāng)我們嵌入一個類型产还,嵌入類型的接受者為指針的方法將不能被外部類型的值訪問匹厘。這也是跟我們上面陳述的接口規(guī)則一致。

回答開頭的問題
現(xiàn)在我們可以寫程序來回答開頭提出的兩個問題了脐区,首先我們讓 Admin 類型實(shí)現(xiàn) Notifier 接口:

Admin 類型實(shí)現(xiàn)的接口顯示一條 admin 方面的信息愈诚。當(dāng)我們使用 Admin 類型的指針去調(diào)用函數(shù) SendNotification 時,這將幫助我們確定到底是哪個接口實(shí)現(xiàn)被調(diào)用了牛隅。

現(xiàn)在創(chuàng)建一個 Admin 類型的值并把它的地址傳入 SendNotification 函數(shù)炕柔,來看看發(fā)生了什么:

package main

import (
    "log"
)

type User struct {
    Name  string
    Email string
}

type Admin struct {
    User
    Level string
}

func (u *User) Notify() error {
    log.Printf("User: Sending User Email To %s<%s>\n",
        u.Name,
        u.Email)

    return nil
}

//func (a *Admin) Notify() error {
//  log.Printf("Admin: Sending Admin Email To %s<%s>\n",
//      a.Name,
//      a.Email)
//
//  return nil
//}

type Notifier interface {
    Notify() error
}

func SendNotification(notify Notifier) error {
    return notify.Notify()
}

func main() {
    admin := &Admin{}
    admin.Notify()
    admin.User.Notify()
    SendNotification(admin)
}

源代碼實(shí)例分享:https://play.golang.org/p/JGhFaJnGpS

  • 編譯器會因為我們同時有兩個接口實(shí)現(xiàn)而報錯嗎?
    不會媒佣,因為當(dāng)我們使用嵌入類型時匕累,類型名充當(dāng)了字段名。嵌入類型作為結(jié)構(gòu)體的內(nèi)部類型包含了自己的字段和方法默伍,且具有唯一的名字欢嘿。所以我們可以有同一接口的內(nèi)部實(shí)現(xiàn)和外部實(shí)現(xiàn)授霸。

  • 如果編譯器接受這樣的定義,那么當(dāng)接口調(diào)用時編譯器要怎么確定該使用哪個實(shí)現(xiàn)际插?
    如果外部類型包含了符合要求的接口實(shí)現(xiàn)碘耳,它將會被使用。否則框弛,通過方法提升辛辨,任何內(nèi)部類型的接口實(shí)現(xiàn)可以直接被外部類型使用。

  • 總結(jié)
    在 Go 語言中瑟枫,方法斗搞,接口和嵌入類型一起工作方式是獨(dú)一無二的。這些特性可以幫助我們像面向?qū)ο竽菢咏M織結(jié)構(gòu)然后達(dá)到同樣的目的慷妙,并且沒有其它復(fù)雜的東西僻焚。用本文中談到的語言特色,我們可以以極少的代碼來構(gòu)建抽象和可伸縮性的框架膝擂。

轉(zhuǎn)自:http://www.jb51.net/article/56831.htm

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末虑啤,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子架馋,更是在濱河造成了極大的恐慌狞山,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件叉寂,死亡現(xiàn)場離奇詭異萍启,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)屏鳍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門勘纯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人钓瞭,你說我怎么就攤上這事驳遵。” “怎么了降淮?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵超埋,是天一觀的道長搏讶。 經(jīng)常有香客問我佳鳖,道長,這世上最難降的妖魔是什么媒惕? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任系吩,我火速辦了婚禮,結(jié)果婚禮上妒蔚,老公的妹妹穿的比我還像新娘穿挨。我一直安慰自己月弛,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布科盛。 她就那樣靜靜地躺著帽衙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪贞绵。 梳的紋絲不亂的頭發(fā)上厉萝,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機(jī)與錄音榨崩,去河邊找鬼谴垫。 笑死,一個胖子當(dāng)著我的面吹牛母蛛,可吹牛的內(nèi)容都是我干的翩剪。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼彩郊,長吁一口氣:“原來是場噩夢啊……” “哼前弯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起秫逝,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤博杖,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后筷登,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體剃根,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年前方,在試婚紗的時候發(fā)現(xiàn)自己被綠了狈醉。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡惠险,死狀恐怖苗傅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情班巩,我是刑警寧澤渣慕,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站抱慌,受9級特大地震影響逊桦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜抑进,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一强经、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧寺渗,春花似錦匿情、人聲如沸兰迫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽汁果。三九已至,卻和暖如春玲躯,著一層夾襖步出監(jiān)牢的瞬間须鼎,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工府蔗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留晋控,地道東北人。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓姓赤,卻偏偏與公主長得像赡译,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子不铆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評論 2 355

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理蝌焚,服務(wù)發(fā)現(xiàn),斷路器誓斥,智...
    卡卡羅2017閱讀 134,659評論 18 139
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法只洒,類相關(guān)的語法,內(nèi)部類的語法劳坑,繼承相關(guān)的語法毕谴,異常的語法,線程的語...
    子非魚_t_閱讀 31,639評論 18 399
  • 多線程距芬、特別是NSOperation 和 GCD 的內(nèi)部原理涝开。運(yùn)行時機(jī)制的原理和運(yùn)用場景。SDWebImage的原...
    LZM輪回閱讀 2,007評論 0 12
  • 周末早起準(zhǔn)備去駕校框仔,沒想到3歲的兒子也早早醒來舀武,一個勁地喊媽媽。過去床邊想哄他繼續(xù)入睡离斩,但是他堅持要起來银舱,哭癟癟地...
    山河萬朵閱讀 542評論 0 3
  • 分析閱讀的第一個規(guī)則:讀者一定要知道讀的是哪一類書,而且要越早知道越好跛梗。最好早在你開始閱讀之前就先知道寻馏。 閱讀首先...
    飛鷹于凱閱讀 141評論 0 0