golang interface 理解探究

栏目: Go · 发布时间: 5年前

内容简介:比如 Java 的 interface 实现需要显示的声明:意味着对于接口的实现都需要显示的声明,在代码编写方面有依赖限制,同时需要处理包的依赖。而ducktype(鸭子类型)意思即为,“看起来像鸭子,走起来像鸭子,叫起来像鸭子即认为是鸭子”,如果一个struct实现了接口中的所有方法,那么它的行为就是这个接口认定的,那么它就是这个接口类型。上文中的

golang interface

1.interface 由来

  • 在很多oop语言中都有接口类型,java中的接口以及c++中的虚基类都是接口的实现。golang中的接口概念类似,但是它有自己的特点:

    • 非侵入式
    • ducktype
    • 泛型
    • 隐藏具体实现

非侵入式

    比如 Java 的 interface 实现需要显示的声明:

public class MyWriter implements io.Writer {}

    意味着对于接口的实现都需要显示的声明,在代码编写方面有依赖限制,同时需要处理包的依赖。而 非侵入式 接口只需实现其包含的方法即可:

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{}) 可以接受任意类型的传入。

隐藏具体实现

    用接口类型作为函数返回值,可以隐藏返回的具体类型。得到的返回值只能依据接口提供的方法执行操作而不用关心或不能看到实际类型的实现细节。

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 指向内存中的实际数据。

  • 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 的指针值的副本

以上所述就是小编给大家介绍的《golang interface 理解探究》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

中国制造2025:产业互联网开启新工业革命

中国制造2025:产业互联网开启新工业革命

夏妍娜、赵胜 / 机械工业出版社 / 2016-2-22 / 49.00

过去20年,是中国消费互联网肆意生长的"黄金20年",诞生了诸如BAT等互联网巨头,而时至今日,风口正逐渐转向了产业互联网。互联网这一摧枯拉朽的飓风,在改造了消费服务业之后,正快速而坚定地横扫工业领域,拉开了产业互联网"关键30年"的大幕。 "中国制造2025"规划,恰是中国政府在新一轮产业革命浪潮中做出的积极举措,是在"新常态"和"供给侧改革"的背景下,强调制造业在中国经济中的基础作用,认......一起来看看 《中国制造2025:产业互联网开启新工业革命》 这本书的介绍吧!

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具