Golang 1.x版本泛型编程

Go是一门天生为服务器程序设计的简洁的语言,所以Go的设计原则聚焦在可扩展性、可读性和并发性,而多态性并非这门语言的设计初衷,所以就被放在了一边。虽然在2.0版本以前尚未泛型的支持,可是Go自带的一些语言特性能够知足一些相似“泛型”的要求,好比内置类型:程序员

  1. arraygolang

  2. slice编程

  3. mapbash

  4. chan服务器

这四种类型能够用任意类型的元素初始化,例如map[yourtype]bool就能够用来实现任意元素的集合。Go的一些内置函数也是通用的,好比len()既能够用于string, array, slice, 也能够用于求map的长度。并发

可是若是golang内置结构和函数不能知足需求,能够从如下几种方法来入手:app

类型断言

当你想实现一个通用函数的时候,会考虑传入的参数是否是固定类型的,Go正好提供了interface{}类型,它能够表明任意类型。当你不肯定用什么类型合适的时候,用它就对了。举个简单的例子:运维

type Container struct {
    elem []interface{}
}

func (this *Container) Put(v interface{}) {
    *this = append(*this, elem)
}
// 取出最后一个元素
func (this *Container) Get() interface{} {
    ret := (*c)[len(*c)-1]
    *c = (*c)[:len(*c)-1]
    return ret
}

func assertExample() {
    container := &Container{}
    container.Put(1)
    container.Put("Hello")
    _, ok := container.Get().(int);!ok {
        fmt.Println("Unable to read an int from container")
    }
}复制代码

经过接口类型咱们把细节彻底隐藏了起来,可是咱们也把运行时类型检查失败的风险留给了调用者,并且调用者每次都要写函数

_, ok := YourType.(type);!ok{}复制代码

这种类型断言,比较啰嗦。工具

反射机制

反射机制就是在运行时动态的调用对象的方法和属性,Python和Java语言都有实现,而Golang是经过reflect包来实现的。反射机制容许程序在编译时处理未知类型的对象,这听上去能够解决咱们上面的问题,如今修改一下代码:

type Container struct {
    elem reflect.Value
}
func NewContainer(t reflect.Type) *Container {
    return &Container{
        elem: reflect.MakeSlice(reflect.SliceOf(t), 0, 10),
    }
}
func (this *Container) Put(v interface{}) {
    if reflect.ValueOf(val).Type() != c.elem.Type().Elem() {
        panic(fmt.Sprintf("Cannot set a %T into a slice of %s", val, c.elem.Type().Elem()))
    }
    c.elem = reflect.Append(c.elem, reflect.ValueOf(val))
}
func (this *Container) Get(regref interface{}) interface{} {
    retref = c.elem.Index(c.elem.Len()-1)
    c.elem = c.elem.Slice(0, c.elem.Len()-1)
    return retref
}
func assertExample() {
    a := 0.123456
    nc := NewContainer(reflect.TypeOf(a))
    nc.Put(a)
    nc.Put(1.11)
    nc.Put(2.22)
    b := 0.0
    c := nc.Get(&b)
    fmt.Println(c)
    d := nc.Get(&b)
    fmt.Println(d)
}复制代码

能够看到使用反射的代码量要增长很多,并且要写各类reflect方法的前缀。这些对于有代码洁癖的人来讲是个灾难,也会让程序员效率变慢,由于没有编译时的类型检查,会带来额外的运行开销。

使用接口

接口有个特色是只作定义无论细节实现,咱们能够利用这一特性实现泛型,例如标准库中的sort包就是使用接口实现对任意容器元素排序的:

type Interface interface {
    Len() int
    Less(i, j int) bool
    Swap(i, j int)
}复制代码

只要实现接口定义的这三种方法,便可对自定义的容器元素进行排序,具体例子能够参考sort包的文档。查看sort包的源码后能够发现代码很是简洁,可是缺点也很明显,使用者须要本身把接口方法从新实现一遍。

代码生成工具

代码生成的原理是先写一个mock类型,这个mock只作占位符使用,而后经过转换工具把这个占位符替换成具体类型,已经有不少人写过了,这里就再也不多说。这样作的缺点是生成的文件比较大,依赖第三方工具和模板语法。

总之,Golang的泛型实现没有一个固定的方法,或者说一个放之四海而皆准的理想方法,程序设计达到必定规模后,老是须要在代码效率,编译器效率和运行效率之间作出一些取舍,所以咱们须要知道实现泛型的不一样方法,在适当的时候使用合适的那个就能够了。


文章首发于小米运维公众号,原文请戳 Golang 1.x版本泛型编程

相关文章
相关标签/搜索