Go之interface的具體使用
淺顯地了解了一下 Go,發(fā)現(xiàn) Go 語法的設計非常簡潔,易于理解。正應了 Go 語言之父 Rob Pike 說的那句“Less is more”—— 大道至簡。
下面就具體的語法特性說說我自己的體會。
interface
概覽
與通常以類型層次與繼承為根基的面向對象設計(OOP)語言(如C++、Java)不同,Go 的核心思想就是組合(composition)。Go 進一步解耦了對象與操作,實現(xiàn)了真正的鴨子類型(Duck typing):一個對象如果能嘎嘎叫那就能當做鴨子,而不是像 C++ 或 Java 那樣需要類型系統(tǒng)去保證:一個對象先得是只鴨子,然后才能嘎嘎叫。
type Duck interface { Quack() } type Animal struct { name string } func (animal Animal) Quack() { fmt.Println(animal.name, ": Quack! Quack! Like a duck!") } func main() { unknownAnimal := Animal{name: "Unknown"} var equivalent Duck equivalent = unknownAnimal equivalent.Quack() }
運行上面的代碼輸出:
Unknown : Quack! Quack! Like a duck!
下面用 Java 語言來實現(xiàn):
interface Duck { void Quack(); } class SomeAnimal implements Duck { String name; public SomeAnimal(String name) { this.name = name; } public void Quack() { System.out.println(name + ": Quack! Quack! I am a duck!"); } } public class Test { public static void main(String []args){ SomeAnimal unknownAnimal = new SomeAnimal("Unknown"); Duck equivalent = unknownAnimal; equivalent.Quack(); } }
兩相比較就能看出:Go 將對象與對其的操作(方法或函數(shù))解耦得更徹底。Go 并不需要一個對象通過類型系統(tǒng)來保證實現(xiàn)了某個接口(is a),而只需要這個對象實現(xiàn)了某個接口的方法即可(like a),而且類型聲明與方法聲明或實現(xiàn)也是松耦合的形式。如果稍微轉換一下方法的實現(xiàn)方式:
func (animal Animal) Quack() { fmt.Println(animal.name, ": Quack! Quack! Like a duck!") }
為:
func Quack(animal Animal) { fmt.Println(animal.name, ": Quack! Quack! Like a duck!") }
是不是就和普通方法并無二致了?
在深入淺出 Cocoa 之消息一文中我曾分析過 Objective C 的消息調用過程:
Bird * aBird = [[Bird alloc] init]; [aBird fly];
中對 fly 的調用,編譯器通過插入一些代碼,將之轉換為對方法具體實現(xiàn) IMP 的調用,這個 IMP 是通過在 Bird 的類結構中的方法鏈表中查找名稱為 fly 的選擇子 SEL 對應的具體方法實現(xiàn)找到的,編譯器會將消息調用轉換為對消息函數(shù) objc_msgSend的調用:
objc_msgSend(aBird, @selector(fly));
無論是 Objective C 的消息機制還是 Qt 中的 Signal/Slot 機制,可以說都是在嘗試將對象本身(數(shù)據(jù))與對對象的操作(消息)解耦,但 Go 將這個工作在語言層面做得更加徹底,這樣不僅避免多重繼承問題,還體現(xiàn)出面向對象設計中最要緊的事情:對象間的消息傳遞。
實現(xiàn)
interface 實際上就是一個結構體,包含兩個成員。其中一個成員是指向具體數(shù)據(jù)的指針,另一個成員中包含了類型信息。空接口和帶方法的接口略有不同,下面分別是空接口和帶方法的接口是使用的數(shù)據(jù)結構:
struct Eface { Type* type; void* data; }; struct Iface { Itab* tab; void* data; }; struct Itab { InterfaceType* inter; Type* type; Itab* link; int32 bad; int32 unused; void (*fun[])(void); }; struct Type { uintptr size; uint32 hash; uint8 _unused; uint8 align; uint8 fieldAlign; uint8 kind; Alg *alg; void *gc; String *string; UncommonType *x; Type *ptrto; };
先看Eface,它是interface{}底層使用的數(shù)據(jù)結構。數(shù)據(jù)域中包含了一個void*指針,和一個類型結構體的指針。interface{}扮演的角色跟C語言中的void*是差不多的,Go中的任何對象都可以表示為interface{}。不同之處在于,interface{}中有類型信息,于是可以實現(xiàn)反射。
不同類型數(shù)據(jù)的類型信息結構體并不完全一致,Type是類型信息結構體中公共的部分,其中size描述類型的大小,UncommonType是指向一個函數(shù)指針的數(shù)組,收集了這個類型的具體實現(xiàn)的所有方法。
在reflect包中有個KindOf函數(shù),返回一個interface{}的Type,其實該函數(shù)就是簡單的取Eface中的Type域。
Iface和Eface略有不同,它是帶方法的interface底層使用的數(shù)據(jù)結構。data域同樣是指向原始數(shù)據(jù)的,Itab中不僅存儲了Type信息,而且還多了一個方法表fun[]。一個Iface中的具體類型中實現(xiàn)的方法會被拷貝到Itab的fun數(shù)組中。
Type的UncommonType中有一個方法表,某個具體類型實現(xiàn)的所有方法都會被收集到這張表中。reflect包中的Method和MethodByName方法都是通過查詢這張表實現(xiàn)的。表中的每一項是一個Method,其數(shù)據(jù)結構如下:
struct Method { String *name; String *pkgPath; Type *mtyp; Type *typ; void (*ifn)(void); void (*tfn)(void); };
Iface的Itab的InterfaceType中也有一張方法表,這張方法表中是接口所聲明的方法。其中每一項是一個IMethod,數(shù)據(jù)結構如下:
struct IMethod { String *name; String *pkgPath; Type *type; };
跟上面的Method結構體對比可以發(fā)現(xiàn),這里是只有聲明沒有實現(xiàn)的。
Iface中的Itab的func域也是一張方法表,這張表中的每一項就是一個函數(shù)指針,也就是只有實現(xiàn)沒有聲明。
類型轉換時的檢測就是看Type中的方法表是否包含了InterfaceType的方法表中的所有方法,并把Type方法表中的實現(xiàn)部分拷到Itab的func那張表中。
注意事項
一個interface在沒有進行初始化時,對應的值是nil。也就是說:
var v interface{}
此時v就是一個nil。在底層存儲上,它是一個空指針。
與之不同的情況
var obj *T var v interface{} v = obj
此時v是一個interface,它的值是nil,也就是說其data域為空,但它自身不為nil。
下面來看個例子就明白了:
Go語言中的error類型實際上是抽象了Error()方法的error接口:
type error interface { Error() string }
有如下代碼:
type Error struct { errCode uint8 } func (e *Error) Error() string { switch e.errCode { default: return "unknown error" } } func test_checkError() { var e *Error if e == nil { fmt.Println("e is nil") } else { fmt.Println("e is not nil") } var err error err = e if err == nil { fmt.Println("err is nil") } else { fmt.Println("err is not nil") } }
運行test_checkError()輸出:
e is nil
err is not nil
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
golang struct 實現(xiàn) interface的方法
這篇文章主要介紹了golang struct 實現(xiàn) interface的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-07-07Golang 實現(xiàn)Socket服務端和客戶端使用TCP協(xié)議通訊
這篇文章主要介紹了Golang 實現(xiàn)Socket服務端和客戶端使用TCP協(xié)議通訊,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12Golang使用singleflight解決并發(fā)重復請求
高并發(fā)的場景下,經(jīng)常會出現(xiàn)并發(fā)重復請求資源的情況,singleflight是golang內置的一個包,這個包提供了對重復函數(shù)調用的抑制功能,所以下面我們就來看看如何使用它解決并發(fā)重復請求吧2023-08-08golang如何通過viper讀取config.yaml文件
這篇文章主要介紹了golang通過viper讀取config.yaml文件,圍繞golang讀取config.yaml文件的相關資料展開詳細內容,需要的小伙伴可以參考一下2022-03-03go中string、int、float相互轉換的實現(xiàn)示例
本文主要介紹了go中string、int、float相互轉換的實現(xiàn)示例,文中根據(jù)實例編碼詳細介紹的十分詳盡,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03Golang動態(tài)數(shù)組的實現(xiàn)示例
動態(tài)數(shù)組能自動調整大小,與靜態(tài)數(shù)組不同,其大小不固定,可根據(jù)需求變化,實現(xiàn)通常依賴于數(shù)據(jù)結構如鏈表或數(shù)組加額外信息,本文就來介紹一下Golang動態(tài)數(shù)組的實現(xiàn)示例,感興趣的可以了解一下2024-10-10