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"