Go 由于不支持泛型而臭名昭著,但最近,泛型已接近成為現(xiàn)實。Go 團隊實施了一個看起來比較穩(wěn)定的設(shè)計草案,并且正以源到源翻譯器原型的形式獲得關(guān)注。本文講述的是泛型的最新設(shè)計,以及如何自己嘗試泛型。
為新城等地區(qū)用戶提供了全套網(wǎng)頁設(shè)計制作服務(wù),及新城網(wǎng)站建設(shè)行業(yè)解決方案。主營業(yè)務(wù)為成都網(wǎng)站設(shè)計、網(wǎng)站制作、新城網(wǎng)站設(shè)計,以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠的服務(wù)。我們深信只要達(dá)到每一位用戶的要求,就會得到認(rèn)可,從而選擇與我們長期合作。這樣,我們也可以走得更遠(yuǎn)!
例子
FIFO Stack
假設(shè)你要創(chuàng)建一個先進(jìn)先出堆棧。沒有泛型,你可能會這樣實現(xiàn):
type?Stack?[]interface{}func?(s?Stack)?Peek()?interface{}?{
return?s[len(s)-1]
}
func?(s?*Stack)?Pop()?{
*s?=?(*s)[:
len(*s)-1]
}
func?(s?*Stack)?Push(value?interface{})?{
*s?=?
append(*s,?value)
}
但是,這里存在一個問題:每當(dāng)你 Peek 項時,都必須使用類型斷言將其從 interface{} 轉(zhuǎn)換為你需要的類型。如果你的堆棧是 *MyObject 的堆棧,則意味著很多 s.Peek().(*MyObject)這樣的代碼。這不僅讓人眼花繚亂,而且還可能引發(fā)錯誤。比如忘記 * 怎么辦?或者如果您輸入錯誤的類型怎么辦?s.Push(MyObject{})` 可以順利編譯,而且你可能不會發(fā)現(xiàn)到自己的錯誤,直到它影響到你的整個服務(wù)為止。
通常,使用 interface{} 是相對危險的。使用更多受限制的類型總是更安全,因為可以在編譯時而不是運行時發(fā)現(xiàn)問題。
泛型通過允許類型具有類型參數(shù)來解決此問題:
type?Stack(type?T)?[]Tfunc?(s?Stack(T))?Peek()?T?{
return?s[len(s)-1]
}
func?(s?*Stack(T))?Pop()?{
*s?=?(*s)[:
len(*s)-1]
}
func?(s?*Stack(T))?Push(value?T)?{
*s?=?
append(*s,?value)
}
這會向 Stack 添加一個類型參數(shù),從而完全不需要 interface{}。現(xiàn)在,當(dāng)你使用 Peek() 時,返回的值已經(jīng)是原始類型,并且沒有機會返回錯誤的值類型。這種方式更安全,更容易使用。(譯注:就是看起來更丑陋,^-^)
此外,泛型代碼通常更易于編譯器優(yōu)化,從而獲得更好的性能(以二進(jìn)制大小為代價)。如果我們對上面的非泛型代碼和泛型代碼進(jìn)行基準(zhǔn)測試,我們可以看到區(qū)別:
type?MyObject?struct?{
X?
int
}
var?sink?MyObjectfunc?BenchmarkGo1(b?*testing.B)?{
for?i?:=?0;?i??b.N;?i++?{
var?s?Stack
s.Push(MyObject{})
s.Push(MyObject{})
s.Pop()
sink?=?s.Peek().(MyObject)
}
}
func?BenchmarkGo2(b?*testing.B)?{
for?i?:=?0;?i??b.N;?i++?{
var?s?Stack(MyObject)
s.Push(MyObject{})
s.Push(MyObject{})
s.Pop()
sink?=?s.Peek()
}
}
結(jié)果:
BenchmarkGo1BenchmarkGo1-16?????12837528?????????87.0?ns/op???????48?B/op????????2?allocs/opBenchmarkGo2BenchmarkGo2-16?????28406479?????????41.9?ns/op???????24?B/op????????2?allocs/op
在這種情況下,我們分配更少的內(nèi)存,同時泛型的速度是非泛型的兩倍。
合約(Contracts)
上面的堆棧示例適用于任何類型。但是,在許多情況下,你需要編寫僅適用于具有某些特征的類型的代碼。例如,你可能希望堆棧要求類型實現(xiàn) String() 函數(shù)
大家好,我是小白,有點黑的那個白。
最近遇到一個問題,因為業(yè)務(wù)需求,需要對接第三方平臺.
而三方平臺提供的一些HTTP(S)接口都有統(tǒng)一的密鑰生成規(guī)則要求.
為此我們封裝了一個獨立的包 xxx-go-sdk 以便維護(hù)和對接使用.
其中核心的部分是自定義HTTP Client,如下:
一些平臺會要求appKey/appSecret等信息,所以Client結(jié)構(gòu)體就變成了這樣,這時參數(shù)還比較少, 而且是必填的參數(shù),我們可以提供構(gòu)造函數(shù)來明確指定。
看起來很滿足,但是當(dāng)我們需要增加一個 Timeout 參數(shù)來控制超時呢?
或許你會說這還不簡單,像下面一樣再加一個參數(shù)唄
那再加些其他的參數(shù)呢?那構(gòu)造函數(shù)的參數(shù)是不是又長又串,而且每個參數(shù)不一定是必須的,有些參數(shù)我們又會考慮默認(rèn)值的問題。
為此,勤勞但尚未致富的 gophers 們使用了總結(jié)一種實踐模式
首先提取所有需要的參數(shù)到一個獨立的結(jié)構(gòu)體 Options,當(dāng)然你也可以用 Configs 啥的.
然后為每個參數(shù)提供設(shè)置函數(shù)
這樣我們就為每個參數(shù)設(shè)置了獨立的設(shè)置函數(shù)。返回值 func(*Options) 看著有點不友好,我們提取下定義為單個 Option 調(diào)整一下代碼
當(dāng)我們需要添加更多的參數(shù)時,只需要在 Options 添加新的參數(shù)并添加新參數(shù)的設(shè)置函數(shù)即可。
比如現(xiàn)在要添加新的參數(shù) Timeout
這樣后續(xù)不管新增多少參數(shù),只需要新增配置項并添加獨立的設(shè)置函數(shù)即可輕松擴展,并且不會影響原有函數(shù)的參數(shù)順序和個數(shù)位置等。
至此,每個選項是區(qū)分開來了,那么怎么作用到我們的 Client 結(jié)構(gòu)體上呢?
首先,配置選項都被提取到了 Options 結(jié)構(gòu)體重,所以我們需要調(diào)整一下 Client 結(jié)構(gòu)體的參數(shù)
其次,每一個選項函數(shù)返回 Option,那么任意多個就是 ...Option,我們調(diào)整一下構(gòu)造函數(shù) NewClient 的參數(shù)形式,改為可變參數(shù),不再局限于固定順序的幾個參數(shù)。
然后循環(huán)遍歷每個選項函數(shù),來生成Client結(jié)構(gòu)體的完整配置選項。
那么怎么調(diào)用呢?對于調(diào)用方而已,直接在調(diào)用構(gòu)造函數(shù)NewClient()的參數(shù)內(nèi)添加自己需要的設(shè)置函數(shù)(WithXXX)即可
當(dāng)需要設(shè)置超時參數(shù),直接添加 WithTimeout即可,比如設(shè)置3秒的超時
配置選項的位置可以任意設(shè)置,不需要受常規(guī)的固定參數(shù)順序約束。
可以看到,這種實踐模式主要作用于配置選項,利用函數(shù)支持的特性來實現(xiàn)的,為此得名 Functional Options Pattern,優(yōu)美的中國話叫做「函數(shù)選項模式」。
最后, 我們總結(jié)回顧一下在Go語言中函數(shù)選項模式的優(yōu)缺點
再簡單不過了,給一個路徑給它,返回文件描述符,如果出現(xiàn)錯誤就會返回一個 *PathError。
這是一個只讀打開模式,實際上就是 os.OpenFile() 的快捷操作,它的原型如下:
Go全稱Golang。
Go語言由Google公司開發(fā),并于2009年開源,相比Java/Python/C等語言,Go尤其擅長并發(fā)編程,性能堪比C語言,開發(fā)效率肩比Python,被譽為“21世紀(jì)的C語言”。
Go語言在云計算、大數(shù)據(jù)、微服務(wù)、高并發(fā)領(lǐng)域應(yīng)用應(yīng)用非常廣泛。BAT大廠正在把Go作為新項目開發(fā)的首選語言。