分享
  1. 首页
  2. 文章

golang interface 理解探究

Zero · · 1066 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

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

有疑问加站长微信联系(非本文作者)

本文来自:Segmentfault

感谢作者:Zero

查看原文:golang interface 理解探究

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
1066 次点击
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏