3 minutes
Golang Face
preface
golang的interface是使用很广泛的一种手段, 相比java python 等语言, golang的interface不需要明显的继承和实现, 只需要实现相应的方法就可以了. 常常我们需要抽象struct的interface, 方便mock进行测试.
detail
golang的interface主要有两种: eface 和 iface. eface是不带方法的interface,
定义参看 runtime2.go 和 type.go,
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 interfacetype struct {
typ _type
pkgpath name
mhdr []imethod
}
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
}
在设计上, iface 比 eface多了一些设计:
- interfaceType: 存放interface的公共描述, 类似 maptype、arraytype、chantype.
- iface使用了itab存储了类型相关信息.
- iface和eface的data指向具体的数据
- _type是所有类型信息结构的公共部分
类型转换
在 iface.go 中提供了 convT2E convT2I convI2I 等方法, E I T 分别表示 eface、iface、具体类型, 除了具体类型和eface iface之间的类型转换
通常我们会用interface定义接口类型, 然后通过不同的实现来开发. 比如存储层, 提供 mysql/redis/mem 三种形式, 亦或者 代理方式.
这里我们使用模拟rpc调用客户端进行演示, 如下: 第一种显示转换:
type HelloClient interface{
SayHello()
}
type MockHelloClient struct{}
func (MockHelloClient) SayHello() {
println("mock hello")
}
func TestHelloClient(c HelloClient){
c.SayHello()
}
func main(){
mc := HelloClient(MockHelloClient())
TestHelloClient(mc)
}
其实, 更常见的是错误码/枚举的定制, 比如
type ErrCode int32
第二种被动转换:
type HelloClient interface{
SayHello()
}
type MockHelloClient struct{}
func (MockHelloClient) SayHello() {
println("mock hello")
}
func TestHelloClient(c HelloClient){
c.SayHello()
}
func main(){
var mc MockHelloClient
TestHelloClient(mc)
}
看下汇编的实现
go build -gcflags '-l' -o main main.go
go tool objdump -s "main\.TestHelloClient" main
得到如下图的内容
TEXT main.TestHelloClient(SB) /Users/snow_young/go/src/inter/main.go
main.go:13 0x104e1a0 65488b0c2530000000 MOVQ GS:0x30, CX
main.go:13 0x104e1a9 483b6110 CMPQ 0x10(CX), SP
main.go:13 0x104e1ad 762c JBE 0x104e1db
main.go:13 0x104e1af 4883ec10 SUBQ $0x10, SP
main.go:13 0x104e1b3 48896c2408 MOVQ BP, 0x8(SP)
main.go:13 0x104e1b8 488d6c2408 LEAQ 0x8(SP), BP
main.go:14 0x104e1bd 488b442418 MOVQ 0x18(SP), AX
main.go:14 0x104e1c2 488b4018 MOVQ 0x18(AX), AX
main.go:14 0x104e1c6 488b4c2420 MOVQ 0x20(SP), CX
main.go:14 0x104e1cb 48890c24 MOVQ CX, 0(SP)
main.go:14 0x104e1cf ffd0 CALL AX
main.go:15 0x104e1d1 488b6c2408 MOVQ 0x8(SP), BP
main.go:15 0x104e1d6 4883c410 ADDQ $0x10, SP
main.go:15 0x104e1da c3 RET
main.go:13 0x104e1db e80089ffff CALL runtime.morestack_noctxt(SB)
main.go:13 0x104e1e0 ebbe JMP main.TestHelloClient(SB)
按照 这篇文章的说法, 0x18(SP) 是 itab的地址, 这里进行了函数地址的存储.
因为是link的缘故, 我们并不能直观的看到函数的调用.
类型断言
iface.go中提供了 assertI2I assertI2I2 assertE2I assertE2I2 等断言方法. 什么时候使用断言呢? 在rpc调用中, 我们可能需要传递一些上下文信息, 通常我们会通过ctx传递, 如下:
import "context"
type baseKey struct{}
type reqBase struct {
LogId int64
from_ip string
ip string
}
func main() {
c := context.Background()
c = context.WithValue(c, baseKey{}, &reqBase{LogId: 11, from_ip: "127.0.0.1", ip: "127.0.0.1"})
baseInfo := c.Value(baseKey{}).(*reqBase)
println("base info: ", baseInfo.LogId, baseInfo.from_ip)
}
如果ctx中明确传递了上下文信息的, 可以直接断言. 不确定的情况下, 建议使用:
func main() {
c := context.Background()
c = context.WithValue(c, baseKey{}, &reqBase{LogId: 11, from_ip: "127.0.0.1", ip: "127.0.0.1"})
baseInfo, ok := c.Value(baseKey{}).(*reqBase)
if !ok{
println("convert failed.")
return
}
println("base info: ", baseInfo.LogId, baseInfo.from_ip)
}
如果使用objdump, 如果不是显式的转换, 很难找到相关的函数调用.
动态分发
反射
484 Words
2019-04-21 10:54 +0800