golang interface 理解探究

golang interface

1.interface 由來

  • 在不少oop語言中都有接口類型,java中的接口以及c++中的虛基類都是接口的實現。golang中的接口概念相似,可是它有本身的特色:java

    • 非侵入式
    • ducktype
    • 泛型
    • 隱藏具體實現

非侵入式

    好比 Java 的 interface 實現須要顯示的聲明:c++

public class MyWriter implements io.Writer {}

    意味着對於接口的實現都須要顯示的聲明,在代碼編寫方面有依賴限制,同時須要處理包的依賴。而非侵入式接口只需實現其包含的方法便可:golang

type IO struct {}

    func (io *IO) Read(p []byte) (n int, err error) {...}
    func (io *IO) Write(p []byte) (n int, err error) {...}
    
    // io package
    type Reader interface {
        Read(p []byte) (n int, err error)
    }
    
    type Writer interface {
        Write(p []byte) (n int, err error)
    }
    
    type ReadWriter interface {
        Reader
        Writer
    }
這種寫法很方便,不用引入包依賴。interface底層實現的時候會動態的檢測。但也會引入一些問題:

1.性能降低。使用interface做爲函數參數,runtime 的時候會動態的肯定行爲。使用具體類型則會在編譯期就肯定類型。

2.不能清楚的看出struct實現了哪些接口,須要藉助ide或其它工具。

ducktype

    ducktype(鴨子類型)意思即爲,「看起來像鴨子,走起來像鴨子,叫起來像鴨子即認爲是鴨子」,若是一個struct實現了接口中的全部方法,那麼它的行爲就是這個接口認定的,那麼它就是這個接口類型。上文中的 IO 實現了 Reader中方法,那麼它就是一個 Reader 類型。編程

泛型編程

    從編譯角度來看,golang並不支持泛型編程。但能夠藉助 ducktype 實現語義上的泛型。但仍是限制在接口類型的範圍以內,更普遍可用 interface{} 來替換參數,而實現泛型。數組

type IO struct {}

    func (io *IO) Read(p []byte) (n int, err error) {...}
    func (io *IO) Write(p []byte) (n int, err error) {...}
    
    // io package
    type Reader interface {
        Read(p []byte) (n int, err error)
    }
    
    type Writer interface {
        Write(p []byte) (n int, err error)
    }
    
    type ReadWriter interface {
        Reader
        Writer
    }
    
    func Print(reader Reader) {
        。。。
    }
    
    func Print2(v interface{}) {
        。。。
    }

    任意類型只要實現了 Reader 便可被做爲參數傳入 func Print(reader Reader)。由於任意類型都實現了空接口,func Print2(v interface{}) 能夠接受任意類型的傳入。ide

隱藏具體實現

    用接口類型做爲函數返回值,能夠隱藏返回的具體類型。獲得的返回值只能依據接口提供的方法執行操做而不用關心或不能看到實際類型的實現細節。函數


2.interface的實現

    在runtime中的實現中有兩種接口類型對應:eface(空接口)和 ifcace (非空接口)工具

type iface struct {
        tab  *itab
        data unsafe.Pointer
    }
    
    type eface struct {
        _type *_type
        data  unsafe.Pointer
    }
    
    type itab struct {
        inter *interfacetype
        _type *_type
        hash  uint32 // copy of _type.hash. Used for type switches.
        _     [4]byte
        fun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
    }
    
    type _type struct {
        size       uintptr
        ptrdata    uintptr // size of memory prefix holding all pointers
        hash       uint32
        tflag      tflag
        align      uint8
        fieldalign uint8
        kind       uint8
        alg        *typeAlg
        // gcdata stores the GC type data for the garbage collector.
        // If the KindGCProg bit is set in kind, gcdata is a GC program.
        // Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
        gcdata    *byte
        str       nameOff
        ptrToThis typeOff
    }
    
    type imethod struct {
        name nameOff
        ityp typeOff
    }
    
    type interfacetype struct {
        typ     _type
        pkgpath name
        mhdr    []imethod
    }

    實現對應的struct如上,eface和iface從內存佈局上都是type point + data point,type point 指向類型信息,data point 指向內存中的實際數據。oop

  • ifcace(非空接口)中的 itab 存儲了實現的具體信息,在其內含的 interfacetype 中記錄了 struct 的元信息以及包路徑,實現的方法(mhdr)等。tab 中 fun 是一個長度爲1的uintptr數組,該數組存儲了實現方法的函數地址,該數組內動態分配,且會依據函數名進行排序。
func itabAdd(m *itab) {
        // Bugs can lead to calling this while mallocing is set,
        // typically because this is called while panicing.
        // Crash reliably, rather than only when we need to grow
        // the hash table.
        if getg().m.mallocing != 0 {
            throw("malloc deadlock")
        }
    
        t := itabTable
        if t.count >= 3*(t.size/4) { // 75% load factor
            // Grow hash table.
            // t2 = new(itabTableType) + some additional entries
            // We lie and tell malloc we want pointer-free memory because
            // all the pointed-to values are not in the heap.
            t2 := (*itabTableType)(mallocgc((2+2*t.size)*sys.PtrSize, nil, true))
            t2.size = t.size * 2
    
            // Copy over entries.
            // Note: while copying, other threads may look for an itab and
            // fail to find it. That's ok, they will then try to get the itab lock
            // and as a consequence wait until this copying is complete.
            iterate_itabs(t2.add)
            if t2.count != t.count {
                throw("mismatched count during itab table copy")
            }
            // Publish new hash table. Use an atomic write: see comment in getitab.
            atomicstorep(unsafe.Pointer(&itabTable), unsafe.Pointer(t2))
            // Adopt the new table as our own.
            t = itabTable
            // Note: the old table can be GC'ed here.
        }
        t.add(m)
    }
  • eface (空接口) 只存儲了 struct 的類型信息和實際數據。
  • struct 實際值按需轉換爲 iface 或 eface。如下爲轉換函數:
func convT2I(tab *itab, elem unsafe.Pointer) (i iface) {
        t := tab._type
        if raceenabled {
            raceReadObjectPC(t, elem, getcallerpc(), funcPC(convT2I))
        }
        if msanenabled {
            msanread(elem, t.size)
        }
        x := mallocgc(t.size, t, true)
        typedmemmove(t, x, elem)
        i.tab = tab
        i.data = x
        return
    }
    
    func convT2E(t *_type, elem unsafe.Pointer) (e eface) {
        if raceenabled {
            raceReadObjectPC(t, elem, getcallerpc(), funcPC(convT2E))
        }
        if msanenabled {
            msanread(elem, t.size)
        }
        x := mallocgc(t.size, t, true)
        // TODO: We allocate a zeroed object only to overwrite it with actual data.
        // Figure out how to avoid zeroing. Also below in convT2Eslice, convT2I, convT2Islice.
        typedmemmove(t, x, elem)
        e._type = t
        e.data = x
        return
    }

3. 編譯器如何判斷是否實現接口

    經過 iface 中的 tab 內的 interfacetype 中的 mhdr 便可獲取類型實現的函數列表,只要該列表包含全部的接口聲明函數,則認爲該類型實現了該接口。由於對函數列表已經進行排序,因此檢查時間複雜度爲 O(m+n).佈局

4. 給 interface 賦值

    golang中的賦值操做皆爲值傳遞,對於interface的賦值操做也不例外。

type IO struct {}

    func (io *IO) Read(p []byte) (n int, err error) {...}
    func (io *IO) Write(p []byte) (n int, err error) {...}
    
    // io package
    type Reader interface {
        Read(p []byte) (n int, err error)
    }
    
    type Writer interface {
        Write(p []byte) (n int, err error)
    }
    
    type ReadWriter interface {
        Reader
        Writer
    }
    
    var reader Reader
    io := IO{}
    reader = io //reader保持一份 io 的副本
    reader = &io //reader保持 io 的指針值的副本
相關文章
相關標籤/搜索