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版本泛型編程

相關文章
相關標籤/搜索