Go interface & struct 接口與結(jié)構(gòu)體

interface & struct 接口與結(jié)構(gòu)體

GO 語言的基礎(chǔ)特性 interface 可以理解為一種類型的規(guī)范或者約定皂林。它跟java羽利,C# 不太一樣,不需要顯示說明實現(xiàn)了某個接口杉编,它沒有繼承或子類或 implements 關(guān)鍵字窒篱,只是通過約定的形式,隱式的實現(xiàn) interface 中的方法即可注益。

go 語言中的 interface:

  • interface 是方法聲明的集合
  • 任何類型的對象實現(xiàn)了在interface 接口中聲明的全部方法碴巾,則表明該類型實現(xiàn)了該接口。
  • interface 可以作為一種數(shù)據(jù)類型丑搔,實現(xiàn)了該接口的任何對象都可以給對應(yīng)的接口類型變量賦值厦瓢。
  • interface 可以被任意對象實現(xiàn),一個類型/對象也可以實現(xiàn)多個 interface
  • interface 定義的方法不能重載低匙,如 eat() eat(s string) 不能同時存在

以繼承為特點的 OOP 只是編程世界的一種抽象方式旷痕,在 Golang 的世界里沒有繼承,只有組合和接口顽冶,并且是松散的接口結(jié)構(gòu)欺抗,不強制聲明實現(xiàn)接口。

If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.

翻譯過來就是:如果某個東西長得像鴨子强重,像鴨子一樣游泳绞呈,像鴨子一樣嘎嘎叫,那它就可以被看成是一只鴨子间景。

結(jié)合到 GO 語言佃声,也就是說那些實現(xiàn)了鴨子某個 interface 全部方法的 struct 對象就是鴨子。

單一繼承關(guān)系解決了 is-a 也就是定義問題倘要,因此可以把子類當做父類來對待圾亏。但對于父類不同但又具有某些共同行為的數(shù)據(jù),單一繼承就不能解決了封拧,C++ 采取了多繼承這種復(fù)雜的方式志鹃。

GO 采取更貼近現(xiàn)實世界的網(wǎng)狀結(jié)構(gòu),不同于繼承泽西,GO 語言的接口是松散的結(jié)構(gòu)曹铃,它不和定義綁定。從這一點上來說捧杉,Duck Type 相比傳統(tǒng)的 extends 是更加松耦合的方式陕见,可以同時從多個維度對數(shù)據(jù)進行抽象,找出它們的共同點味抖,使用同一套邏輯來處理评甜。

如果想在一個包中訪問另一個包中結(jié)構(gòu)體的字段,則必須是大寫字母開頭的變量仔涩,即可導(dǎo)出的變量忍坷。

在定義結(jié)構(gòu)體字段時,除字段名稱和數(shù)據(jù)類型外,還可以使用反引號為結(jié)構(gòu)體字段聲明元信息承匣,這種元信息稱為Tag,用于編譯階段關(guān)聯(lián)到字段當中:

type Member struct {
    Id     int    `json:"id,-"`
    Name   string `json:"name"`
    Email  string `json:"email"`
    Gender int    `json:"gender,"`
    Age    int    `json:"age"`
}

以上使用 encoding/json 包編碼或解碼結(jié)構(gòu)體時使用的Tag信息锤悄,在 Member 成員到 Json 鍵值 做對應(yīng)韧骗。Tag由反引號括起來的一系列用空格分隔的 key:"value" 鍵值對組成,如:

Id int `json:"id" gorm:"AUTO_INCREMENT"`

接口使用例子零聚,定義MyString類型與string一樣袍暴,再實現(xiàn) VolwelsFInder 接口的方法,使用時只需要實例化對象并賦予接口即可以訪問接口規(guī)范的方法隶症,rune 是基本數(shù)據(jù)類型 Unicode 字符:

package main

import (  
    "fmt"
)

// 定義interface 
type VowelsFinder interface {  
    FindVowels() []rune
}

// 別名擴展方式
type MyString string

// 實現(xiàn)接口
func (ms MyString) FindVowels() []rune {  
    var vowels []rune
    for _, rune := range ms {
        if rune == 'a' || rune == 'e' || rune == 'i' || rune == 'o' || rune == 'u' {
            vowels = append(vowels, rune)
        }
    }
    return vowels
}

func findType(i interface{}) {
    switch v := i.(type) {
        case VowelsFinder:
            fmt.Println("VowelsFinder type.", v);
        default:
            fmt.Printf("unknown type\n")
    }
}

func main() {
    name := MyString("Sam Anderson") // 類型轉(zhuǎn)換
    findType(name)  // VowelsFinder type.
    fmt.Printf("Vowels are %c", name.FindVowels())
    
    var v VowelsFinder               // 定義一個接口類型的變量
    v = name 
    fmt.Printf("Vowels are %c", v.FindVowels())
}

所有類型都實現(xiàn)了空接口 Empty Interface 表示為 interface{}政模,空接口也是基礎(chǔ)類型,它的作用相當于 Java 中的 Object蚂会×苎可以對任何接口類型進行類型斷言 Type Assertions,類型斷言檢查正確就可以得到期待的類型胁住。類型斷言檢查是對接口類型進行的動態(tài)類型檢查趁猴,語法格式 x.(T),x 是一個接口彪见,T 就是斷言目標類型儡司,通過斷言檢查就可以從操作數(shù)中得到具體的類型數(shù)據(jù)。

如下面嘗試將變量類型 s 轉(zhuǎn)換成 string余指,先將 s 轉(zhuǎn)換成空接口interface{}(v)捕犬,再進行 .(string) 斷言:

val, ok := interface{}(v).(string)

GO 語言的結(jié)構(gòu)體 struct 是組合非繼承,不像其它 OOP 語言那樣通過繼承機制實現(xiàn)類結(jié)構(gòu)的擴展酵镜。

下面例程中碉碉,Being 是最基礎(chǔ)的結(jié)構(gòu)體,Human 組合了 Being笋婿,而 Student 又組合了 Human誉裆。 但是通過 Human.Eat() 方法調(diào)用 Drink() 只能是 Human.Drink()。如果 Student 也實現(xiàn)了 Eat 方法并調(diào)用 Dranking()缸濒,則會調(diào)用自己的 Student.Dranking()足丢。

對 s 分別進行了 Human、Student 類型斷言庇配,輸出結(jié)果可以看到斩跌,s 斷言 Human 是不成功的,因為它是 Student 類型捞慌,兩個結(jié)構(gòu)體是完全不同的類型耀鸦。斷言 IHuman 接口也是成功的,因為 Student 結(jié)構(gòu)實現(xiàn)了 IHuman 接口的所以方法。也就是說看起來叫起來都像鴨子的東西袖订,那就可以認為它是鴨子

package main
 
import "fmt"
 
func main(){
    var h Human
 
    s := Student{Grade: 1, Human: Human{Name: "Jason", Age: 12, Being: Being{IsLive: true}}}
    fmt.Println("student:", s)
    fmt.Println("student ", s.Name, " is alive:", s.IsLive )
 
    // h = s // cannot use s (type Student) as type Human in assignment
    fmt.Println(h)
 
    // Heal(s) // cannot use s (type Student) as type Being in argument to Heal
    Heal(s.Human.Being) // true
 
    s.Drink() // student drinking...
    s.Eat() // human eating... & human drinking...

    human, b := interface{}(s).(Human) // false s is Student but Human
    fmt.Println(human, b)
     
    student, b := interface{}(s).(Student)
    fmt.Println(student, b)

    ihuman, b := interface{}(s).(IHuman)
    fmt.Println(ihuman, b)

}
 
type Being struct {
    IsLive bool
}
 
type Human struct {
    Being
    Name string
    Age int
}

func (h Human) Eat(){
    fmt.Println("human eating...")
    h.Drink()
}
 
func (h Human) Drink(){
    fmt.Println("human drinking...")
}
 
type Student struct {
    Human
    Grade int
}

func (s Student) Drink(){
    fmt.Println("student drinking...")
}

func Heal(b Being){
    fmt.Println(b.IsLive)
}

type IHuman interface {
    Eat()
    Drink()
}

關(guān)于結(jié)構(gòu)體的擴展兩種方式氮帐,嵌入結(jié)構(gòu)體方式與別名擴展方式的總結(jié)參考后續(xù)的 [JSON 解析與擴展已有類型] 小節(jié)。

package main

import (
    "fmt"
    "reflect"
)

type IBase interface {
    Show()
}

type Base struct {
    ID   int    `yaml:"ID" json:"id"`
    Name string `yaml:"NAME" json:"name"`
}

func (this Base) Show() {
    fmt.Printf("#%d - %s\n", this.ID, this.Name)
}

type User struct {
    Base `yaml:"BasePart" json:"basepart"`
}

type Manager struct {
    Base
}

func echoType(i interface{}) {
    switch v := i.(type) {
    case IBase:
        fmt.Println("echoType: IBase.", v)
    }

    switch v := i.(type) {
    case Base:
        fmt.Println("echoType: Base.", v)
    case User:
        fmt.Println("echoType: User.", v)
    case Manager:
        fmt.Println("echoType: Manager.", v)
    default:
        fmt.Printf("echoType: Unknown.\n")
    }
}

func main() {
    var user = User{Base: Base{1, "ABC"}}
    var inte = interface{}(user)
    // panic: interface conversion: interface {} is main.User, not main.Manager
    // var mana = inte.(Manager)
    fmt.Printf("User %#v\n", user)
    fmt.Printf("Inte %#v\n", inte)

    echoType(user)

    // TypeOf return reflect.Type interface
    tt := reflect.TypeOf(&user)
    rt := tt.Elem()
    for i := 0; i < rt.NumField(); i++ {
        field := rt.Field(i)
        at := field.Tag
        yt := field.Tag.Get("yaml")
        jt, _ := field.Tag.Lookup("json")
        fmt.Printf("FIELD: %s\n\tJASON TAG: %s\n\tYAML TAG: %s\n\tALL TAGS: %s\n", field.Name, jt, yt, at)
    }
}

一個對象既至少由兩種類型信息洛姑,如上面代碼中的 user上沐,既是一個 struct 也是一個 interface 類型,以上示例輸出:

User main.User{Base:main.Base{ID:1, Name:"ABC"}}
Inte main.User{Base:main.Base{ID:1, Name:"ABC"}}
echoType: IBase. {{1 ABC}}
echoType: User. {{1 ABC}}
FIELD: Base
    JASON TAG: basepart
    YAML TAG: BasePart
    ALL TAGS: yaml:"BasePart" json:"basepart"
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末楞艾,一起剝皮案震驚了整個濱河市参咙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌硫眯,老刑警劉巖蕴侧,帶你破解...
    沈念sama閱讀 211,423評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異两入,居然都是意外死亡净宵,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,147評論 2 385
  • 文/潘曉璐 我一進店門谆刨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來塘娶,“玉大人,你說我怎么就攤上這事痊夭〉蟀叮” “怎么了?”我有些...
    開封第一講書人閱讀 157,019評論 0 348
  • 文/不壞的土叔 我叫張陵她我,是天一觀的道長虹曙。 經(jīng)常有香客問我,道長番舆,這世上最難降的妖魔是什么酝碳? 我笑而不...
    開封第一講書人閱讀 56,443評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮恨狈,結(jié)果婚禮上疏哗,老公的妹妹穿的比我還像新娘。我一直安慰自己禾怠,他們只是感情好返奉,可當我...
    茶點故事閱讀 65,535評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著吗氏,像睡著了一般芽偏。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上弦讽,一...
    開封第一講書人閱讀 49,798評論 1 290
  • 那天污尉,我揣著相機與錄音,去河邊找鬼。 笑死被碗,一個胖子當著我的面吹牛某宪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播锐朴,決...
    沈念sama閱讀 38,941評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼缩抡,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了包颁?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,704評論 0 266
  • 序言:老撾萬榮一對情侶失蹤压真,失蹤者是張志新(化名)和其女友劉穎娩嚼,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體滴肿,經(jīng)...
    沈念sama閱讀 44,152評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡岳悟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,494評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了泼差。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贵少。...
    茶點故事閱讀 38,629評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖堆缘,靈堂內(nèi)的尸體忽然破棺而出滔灶,到底是詐尸還是另有隱情,我是刑警寧澤吼肥,帶...
    沈念sama閱讀 34,295評論 4 329
  • 正文 年R本政府宣布录平,位于F島的核電站,受9級特大地震影響缀皱,放射性物質(zhì)發(fā)生泄漏斗这。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,901評論 3 313
  • 文/蒙蒙 一啤斗、第九天 我趴在偏房一處隱蔽的房頂上張望表箭。 院中可真熱鬧,春花似錦钮莲、人聲如沸免钻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽伯襟。三九已至,卻和暖如春握童,著一層夾襖步出監(jiān)牢的瞬間姆怪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,978評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留稽揭,地道東北人俺附。 一個月前我還...
    沈念sama閱讀 46,333評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像溪掀,于是被迫代替她去往敵國和親事镣。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,499評論 2 348

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