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