Golang筆記-淺談interface

前言

classinterface在高級語言中是很重要的概念究恤。class是對模型的定義和封裝,interface則是對行為的抽象和封裝杆故。Go語言雖然沒有class,但是有structinterface,以另一種方式實現(xiàn)同樣的效果。

本文將談一談Go語言這與別不同的interface的基本概念和一些需要注意的地方。

聲明interface

type Birds interface {
    Twitter() string
    Fly(high int) bool
}

上面這段代碼聲明了一個名為Birds的接口類型(interface),這個接口包含兩個行為TwitterFly赐纱。
Go語言里面脊奋,聲明一個接口類型需要使用type關(guān)鍵字、接口類型名稱疙描、interface關(guān)鍵字和一組有{}括起來的方法聲明诚隙,這些方法聲明只有方法名、參數(shù)和返回值起胰,不需要方法體久又。

Go語言沒有繼承的概念,那如果需要實現(xiàn)繼承的效果怎么辦效五?Go的方法是嵌入地消。

type Chicken interface {
    Bird
    Walk()
}

上面這段代碼中聲明了一個新的接口類型Chicken,我們希望他能夠共用Birds的行為畏妖,于是直接在Chicken的接口類型聲明中脉执,嵌入Birds接口類型,這樣Chicken接口中就有了原屬于BirdsTwitterFly這兩個行為以及新增加的Walk行為戒劫,實現(xiàn)了接口繼承的效果半夷。

實現(xiàn)interface

在java中,通過類來實現(xiàn)接口迅细。一個類需要在聲明通過implements顯示說明實現(xiàn)哪些接口巫橄,并在類的方法中實現(xiàn)所有的接口方法。Go語言沒有類茵典,也沒有implements湘换,如何來實現(xiàn)一個接口呢?這里就體現(xiàn)了Go與別不同的地方了统阿。

首先彩倚,Go語言沒有類但是有struct,通過struct來定義模型結(jié)構(gòu)和方法扶平。

其次帆离,Go語言實現(xiàn)一個接口并不需要顯示聲明,而是只要你實現(xiàn)了接口中的所有方法就認(rèn)為你實現(xiàn)了這個接口蜻直。這稱之為Duck typing盯质。

如果它走起步來像鴨子,并且叫聲像鴨子, 那個它一定是一只鴨子.

說道這里袁串,就需要介紹下struct如何實現(xiàn)方法概而。

type Sparrow struct {
    name string
}

func (s *Sparrow) Fly(hign int) bool {
    // ...
    return true
}

func (s *Sparrow) Twitter() string {
    // ...
    return fmt.Sprintf("%s,jojojo", s.name)
}

上面這段代碼,聲明了一個名為Sparrowstruct囱修,下面聲明了兩個方法赎瑰。不過這個方法的聲明行為可能略微有點奇怪。

比如func (s *Sparrow) Fly(hign int) bool中破镰,func關(guān)鍵字用于聲明方法和函數(shù)餐曼,后面方法Fly以及參數(shù)和返回值压储。但是在func關(guān)鍵字和方法名Fly中間還有s *Sparraw的聲明,這個聲明在Go中稱之為接收者聲明源譬,其中s代表這個方法的接收者集惋,*Sparrow代表這個接收者的類型。

接收者的類型可以為一個數(shù)據(jù)類型的指針類型踩娘,也可以是數(shù)據(jù)類型本身刮刑,比如我們針對Sparrow再實現(xiàn)一個方法:

func (s Sparrow) Walk() {
    // ...
}

接收者為數(shù)據(jù)類型的方法稱為值方法,接收者為指針類型的方法稱之為指針方法养渴。

這種非侵入式的接口實現(xiàn)方式非常的方便和靈活雷绢,不用去管理各種接口依賴,對開發(fā)人員來說也更簡潔理卑。

使用interface

利用struct去實現(xiàn)接口之后翘紊,我們就可以用這個struct作為接口參數(shù),使用那些接收接口參數(shù)的方法完成我們的功能藐唠。這也是面向接口編程的方式帆疟,我們的功能依據(jù)接口來實現(xiàn),而不用關(guān)心實現(xiàn)接口的是什么中捆,這樣大大提供了功能的通用性可擴(kuò)展性鸯匹。

func BirdAnimation(bird Birds, high int) {
    fmt.Printf("BirdAnimation of %T\n", bird)
    bird.Twitter()
    bird.Fly(high)
}

func main() {
    var bird Birds
    sparrow := &Sparrow{}
    bird = sparrow
    BirdAnimation(bird, 1000)
    // 或者將sparrow直接作為參數(shù)
    BirdAnimation(sparrow, 1000)
}

上面這段代碼中,我們聲明了一個Birds接口類型的變量bird泄伪,由于*Sparrow實現(xiàn)了Birds接口的所有方法殴蓬,所以我們可以將*Sparrow類型的變量sparrow 賦值給bird◇危或者直接將sparrow作為參數(shù)調(diào)用BirdAnimation染厅,運(yùn)行結(jié)果如下:

?  go run main.go
BirdAnimation of *main.Sparrow
Sparrow Twitter
Sparrow Fly
BirdAnimation of *main.Sparrow
Sparrow Twitter
Sparrow Fly

深入一步interface

關(guān)于空interface

先看一段代碼,猜猜會輸出什么津函。

func NilInterfaceTest(chicken Chicken) {
    if chicken == nil {
        fmt.Println("Sorry,It’s Nil")
    } else {
        fmt.Println("Animation Start!")
        ChickenAnimation(chicken)
    }
}

func main() {
  var sparrow3 *Sparrow
  NilInterfaceTest(sparrow3)
}

我們聲明了一個*Sparrow的變量sparrow3肖粮,但是我們并沒有對其進(jìn)行初始化,是一個nil值尔苦,然后我們直接將它作為參數(shù)調(diào)用NilInterfaceTest()涩馆,我們預(yù)期的結(jié)果是希望NilInterfaceTest方法檢測出nil值,避免出錯允坚。然而實際結(jié)果是這樣的:

?  go run main.go
Animation Start!
ChickenAnimation of *main.Sparrow
panic: value method main.Sparrow.Walk called using nil *Sparrow pointer

goroutine 1 [running]:
...

NilInterfaceTest方法并沒有檢測到我們傳的是一個nil的sparrow魂那,正常去使用最終導(dǎo)致了程序panic。

也許這里很讓人迷惑稠项,其實這里應(yīng)該認(rèn)識到雖然我們可以將實現(xiàn)了接口所有方法的接收者當(dāng)做接口來使用涯雅,但是兩者并不是完全等同。在Go語言中展运,interface的底層結(jié)構(gòu)其實是比較復(fù)雜的活逆,簡要來說精刷,一個interface結(jié)構(gòu)包含兩部分:1.這個接口值的類型;2.指向這個接口值的指針蔗候。我們稍微在NilInterfaceTest代碼中加點東西看看:

func NilInterfaceTest(chicken Chicken) {
    if chicken == nil {
        fmt.Println("Sorry,It’s Nil")
    } else {
        fmt.Println("Animation Start!")
        fmt.Printf("type:%v,value:%v\n", reflect.TypeOf(chicken), reflect.ValueOf(chicken))
        ChickenAnimation(chicken)
    }
}

我們增加了第6行的代碼怒允,將bird變量的類型和值分別輸出,得到結(jié)果如下:

?  go run main.go
Animation Start!
type:*main.Sparrow,value:<nil>
ChickenAnimation of *main.Sparrow
panic: value method main.Sparrow.Walk called using nil *Sparrow pointer
...

我們可以看到bird的type為*main.Sparrow锈遥,而value為nil误算。也就是說,我們將一個nil的*Sparrow賦值給bird后迷殿,這個bird的type部分就已經(jīng)有值了儿礼,只不過他的value部分是nil,所以bird并不是nil庆寺。

關(guān)于方法列表

再看一段代碼:

func ChickenAnimation(chicken Chicken) {
    fmt.Printf("ChickenAnimation of %T\n", chicken)
    chicken.Walk()
    chicken.Twitter()
}

func main() {
    var chicken Chicken
    sparrow2 := Sparrow{}
    chicken = sparrow2
    ChickenAnimation(chicken)
}

其運(yùn)行結(jié)果如下:

?  go run main.go
# command-line-arguments
./main.go:70:10: cannot use sparrow2 (type Sparrow) as type Chicken in assignment:
        Sparrow does not implement Chicken (Fly method has pointer receiver)

編譯器編譯報錯蚊夫,它說Sparrow并沒有實現(xiàn)Chicken接口,因為Fly方法的接受者是指針接收者懦尝,而我們給的是Sparrow知纷。

我們將程序做一點小小的調(diào)整就可以了,將第10行代碼修改為:

chicken = &sparrow2

也許你會問:"Chicken接口的Walk方法的接收者是非指針的Sparrow陵霉,我們把*Sparrow賦值給Chicken接口變量為什么可以通過琅轧?"。

這里就要講到方法列表的概念踊挠。

首先乍桂,一個指針類型的方法列表必然包含所有接收者為指針接收者的方法,同理非指針類型的方法列表也包含所有接收者為非指針類型的方法效床。在我們例子中*Sparrow首先包含:FlyTwitter睹酌;Sparrow包含Walk

其次剩檀,當(dāng)我們擁有一個指針類型的時候憋沿,因為有了這個變量的地址,我們得到這個具體的變量沪猴,所以一個指針類型的方法列表還可以包含其非指針類型作為接收者的方法辐啄。在我們的例子中就是*Sparrow的方法列表為:FlyTwitterWalk运嗜,所以chicken = &sparrow2可以通過壶辜。

但是一個非指針類型卻并不總是能取到它的地址,從而獲取它接收者為指針接收者的方法洗出。所以非指針類型的方法列表中只有接收者為非指針類型的方法士复。如果它的方法列表不能完全覆蓋這個接口图谷,是不算實現(xiàn)了這個接口的翩活。

舉個簡單的例子:

type TestInt int

func main() {
  &TestInt(7)
}

編譯報錯阱洪,無法取址:

?  go run main.go
# command-line-arguments
./main.go:77:2: cannot take the address of TestInt(7)
./main.go:77:2: &TestInt(7) evaluated but not used

又或者:

func main() {
    sparrow4 := Sparrow{}
    sparrow4.Twitter()
}

這樣可以正常運(yùn)行,但是稍微改改:

func main() {
    Sparrow{}.Twitter()
}

則編譯報錯:

?  go run main.go
# command-line-arguments
./main.go:80:11: cannot call pointer method on Sparrow literal
./main.go:80:11: cannot take the address of Sparrow literal

字面量也無法取址菠镇。
因此在使用接口時冗荸,我們要注意不同類型的方法列表,是否實現(xiàn)接口利耍。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蚌本,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子隘梨,更是在濱河造成了極大的恐慌程癌,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件轴猎,死亡現(xiàn)場離奇詭異嵌莉,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)捻脖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進(jìn)店門锐峭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人可婶,你說我怎么就攤上這事沿癞。” “怎么了矛渴?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵椎扬,是天一觀的道長。 經(jīng)常有香客問我具温,道長盗舰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任桂躏,我火速辦了婚禮钻趋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘剂习。我一直安慰自己蛮位,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布鳞绕。 她就那樣靜靜地躺著失仁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪们何。 梳的紋絲不亂的頭發(fā)上萄焦,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼拂封。 笑死茬射,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的冒签。 我是一名探鬼主播在抛,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼萧恕!你這毒婦竟也來了刚梭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤票唆,失蹤者是張志新(化名)和其女友劉穎朴读,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體走趋,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡磨德,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了吆视。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片典挑。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖啦吧,靈堂內(nèi)的尸體忽然破棺而出您觉,到底是詐尸還是另有隱情,我是刑警寧澤授滓,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布琳水,位于F島的核電站,受9級特大地震影響般堆,放射性物質(zhì)發(fā)生泄漏在孝。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一淮摔、第九天 我趴在偏房一處隱蔽的房頂上張望私沮。 院中可真熱鬧,春花似錦和橙、人聲如沸仔燕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽晰搀。三九已至,卻和暖如春办斑,著一層夾襖步出監(jiān)牢的瞬間外恕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留鳞疲,地道東北人罪郊。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像建丧,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子波势,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,446評論 2 348

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