Go語言 interface介紹

2018-07-25 15:58 更新

interface是Go語言中最成功的設(shè)計之一,空的interface可以被當作“鴨子”類型使用,它使得Go這樣的靜態(tài)語言擁有了一定的動態(tài)性,但卻又不損失靜態(tài)語言在類型安全方面擁有的編譯時檢查的優(yōu)勢。

依賴于接口而不是實現(xiàn),優(yōu)先使用組合而不是繼承,這是程序抽象的基本原則。但是長久以來以C++為代表的“面向?qū)ο蟆闭Z言曲解了這些原則,讓人們走入了誤區(qū)。為什么要將方法和數(shù)據(jù)綁死?為什么要有多重繼承這么變態(tài)的設(shè)計?面向?qū)ο笾凶顝娬{(diào)的應(yīng)該是對象間的消息傳遞,卻為什么被演繹成了封裝繼承和多態(tài)。面向?qū)ο笫欠駥崿F(xiàn)程序程序抽象的合理途徑,又或者是因為它存在我們就認為它合理了。歷史原因,中間出現(xiàn)了太多的錯誤。不管怎么樣,Go的interface給我們打開了一扇新的窗。

那么,Go中的interface在底層是如何實現(xiàn)的呢?

Eface和Iface

interface實際上就是一個結(jié)構(gòu)體,包含兩個成員。其中一個成員是指向具體數(shù)據(jù)的指針,另一個成員中包含了類型信息??战涌诤蛶Х椒ǖ慕涌诼杂胁煌?,下面分別是空接口和帶方法的接口是使用的數(shù)據(jù)結(jié)構(gòu):

struct Eface
{
    Type*    type;
    void*    data;
};
struct Iface
{
    Itab*    tab;
    void*    data;
};

先看Eface,它是interface{}底層使用的數(shù)據(jù)結(jié)構(gòu)。數(shù)據(jù)域中包含了一個void*指針,和一個類型結(jié)構(gòu)體的指針。interface{}扮演的角色跟C語言中的void*是差不多的,Go中的任何對象都可以表示為interface{}。不同之處在于,interface{}中有類型信息,于是可以實現(xiàn)反射。

類型信息的結(jié)構(gòu)體定義如下:

struct Type
{
    uintptr size;
    uint32 hash;
    uint8 _unused;
    uint8 align;
    uint8 fieldAlign;
    uint8 kind;
    Alg *alg;
    void *gc;
    String *string;
    UncommonType *x;
    Type *ptrto;
};

其實在前面我們已經(jīng)見過它了。精確的垃圾回收中,就是依賴Type結(jié)構(gòu)體中的gc域的。不同類型數(shù)據(jù)的類型信息結(jié)構(gòu)體并不完全一致,Type是類型信息結(jié)構(gòu)體中公共的部分,其中size描述類型的大小,hash數(shù)據(jù)的hash值,align是對齊,fieldAlgin是這個數(shù)據(jù)嵌入結(jié)構(gòu)體時的對齊,kind是一個枚舉值,每種類型對應(yīng)了一個編號。alg是一個函數(shù)指針的數(shù)組,存儲了hash/equal/print/copy四個函數(shù)操作。UncommonType是指向一個函數(shù)指針的數(shù)組,收集了這個類型的實現(xiàn)的所有方法。

在reflect包中有個KindOf函數(shù),返回一個interface{}的Type,其實該函數(shù)就是簡單的取Eface中的Type域。

Iface和Eface略有不同,它是帶方法的interface底層使用的數(shù)據(jù)結(jié)構(gòu)。data域同樣是指向原始數(shù)據(jù)的,而Itab的結(jié)構(gòu)如下:

struct    Itab
{
    InterfaceType*    inter;
    Type*    type;
    Itab*    link;
    int32    bad;
    int32    unused;
    void    (*fun[])(void);
};

Itab中不僅存儲了Type信息,而且還多了一個方法表fun[]。一個Iface中的具體類型中實現(xiàn)的方法會被拷貝到Itab的fun數(shù)組中。

具體類型向接口類型賦值

將具體類型數(shù)據(jù)賦值給interface{}這樣的抽象類型,中間會涉及到類型轉(zhuǎn)換操作。從接口類型轉(zhuǎn)換為具體類型(也就是反射),也涉及到了類型轉(zhuǎn)換。這個轉(zhuǎn)換過程中做了哪些操作呢?先看將具體類型轉(zhuǎn)換為接口類型。如果是轉(zhuǎn)換成空接口,這個過程比較簡單,就是返回一個Eface,將Eface中的data指針指向原型數(shù)據(jù),type指針會指向數(shù)據(jù)的Type結(jié)構(gòu)體。

將某個類型數(shù)據(jù)轉(zhuǎn)換為帶方法的接口時,會復(fù)雜一些。中間涉及了一道檢測,該類型必須要實現(xiàn)了接口中聲明的所有方法才可以進行轉(zhuǎn)換。這個檢測是在編譯過程中做的,我們可以做個測試:

type I interface {
    String()
}
var a int = 5
var b I = a

編譯會報錯:

cannot use a (type int) as type I in assignment:
    int does not implement I (missing String method)

說明具體類型轉(zhuǎn)換為帶方法的接口類型是在編譯過程中進行檢測的。

那么這個檢測是如何實現(xiàn)的呢?在runtime下找到了iface.c文件,應(yīng)該是早期版本是在運行時檢測留下的,其中有一個itab函數(shù)就是判斷某個類型是否實現(xiàn)了某個接口,如果是則返回一個Itab結(jié)構(gòu)體。

類型轉(zhuǎn)換時的檢測就是比較具體類型的方法表和接口類型的方法表,看具體類型是實現(xiàn)了接口類型所聲明的所有的方法。還記得Type結(jié)構(gòu)體中是有個UncommonType字段的,里面有張方法表,類型所實現(xiàn)的方法都在里面。而在Itab中有個InterfaceType字段,這個字段中也有一張方法表,就是這個接口所要求的方法。這兩處方法表都是排序過的,只需要一遍順序掃描進行比較,應(yīng)該可以知道Type中否實現(xiàn)了接口中聲明的所有方法。最后還會將Type方法表中的函數(shù)指針,拷貝到Itab的fun字段中。

這里提到了三個方法表,有點容易把人搞暈,所以要解釋一下。

Type的UncommonType中有一個方法表,某個具體類型實現(xiàn)的所有方法都會被收集到這張表中。reflect包中的Method和MethodByName方法都是通過查詢這張表實現(xiàn)的。表中的每一項是一個Method,其數(shù)據(jù)結(jié)構(gòu)如下:

struct Method
{
    String *name;
    String *pkgPath;
    Type    *mtyp;
    Type *typ;
    void (*ifn)(void);
    void (*tfn)(void);
};

Iface的Itab的InterfaceType中也有一張方法表,這張方法表中是接口所聲明的方法。其中每一項是一個IMethod,數(shù)據(jù)結(jié)構(gòu)如下:

struct IMethod
{
    String *name;
    String *pkgPath;
    Type *type;
};

跟上面的Method結(jié)構(gòu)體對比可以發(fā)現(xiàn),這里是只有聲明沒有實現(xiàn)的。

Iface中的Itab的func域也是一張方法表,這張表中的每一項就是一個函數(shù)指針,也就是只有實現(xiàn)沒有聲明。

類型轉(zhuǎn)換時的檢測就是看Type中的方法表是否包含了InterfaceType的方法表中的所有方法,并把Type方法表中的實現(xiàn)部分拷到Itab的func那張表中。

reflect

reflect就是給定一個接口類型的數(shù)據(jù),得到它的具體類型的類型信息,它的Value等。reflect包中的TypeOf和ValueOf函數(shù)分別做這個事情。

還有像

    v, ok := i.(T)

這樣的語法,也是判斷一個接口i的具體類型是否為類型T,如果是則將其值返回給v。這跟上面的類型轉(zhuǎn)換一樣,也會檢測轉(zhuǎn)換是否合法。不過這里的檢測是在運行時執(zhí)行的。在runtime下的iface.c文件中,有一系統(tǒng)的assetX2X函數(shù),比如runtime.assetE2T,runtime.assetI2T等等。這個實現(xiàn)起來比較簡單,只需要比較Iface中的Itab的type是否與給定Type為同一個。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號