共计 4513 个字符,预计需要花费 12 分钟才能阅读完成。
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 的指针值的副本