責(zé)任鏈模式(chain of responsibility)是赫赫有名的GoF書中描述的23個(gè)經(jīng)典模式之一。
這個(gè)模式通過將請(qǐng)求的處理者組織成一個(gè)鏈條孝扛,有效避免請(qǐng)求發(fā)送者與多個(gè)請(qǐng)求處理者耦合在一起。
經(jīng)典實(shí)現(xiàn)
具體到實(shí)現(xiàn)這一塊幽崩,書中和網(wǎng)上提供的內(nèi)容苦始,大部分都是拿Java舉例,即使用其它語言如python慌申,也基本都是面向?qū)ο筮@那一套陌选。
一般都是先給你一個(gè)這樣的圖。
然后定義Handler接口和若干個(gè)實(shí)現(xiàn)了接口的Receiver對(duì)象蹄溉。
不滿之處
但是如果你像我一樣咨油,用go語言,『函數(shù)是一等公民』柒爵,卻還在定義結(jié)構(gòu)體和方法役电,就顯得有點(diǎn)笨重了。
不管你們滿不滿意棉胀,反正我是不滿意法瑟。
第一,看起來很臃腫唁奢,不符合go語言極簡設(shè)計(jì)的思想霎挟。
第二,沒有利用好go語言的特性麻掸。
所以酥夭,我用函數(shù)式風(fēng)格重新實(shí)現(xiàn)了一遍。
重新實(shí)現(xiàn)
實(shí)現(xiàn)一個(gè)責(zé)任鏈有幾個(gè)步驟:
- 定義統(tǒng)一的Handler接口
- 按Handler的定義论笔,實(shí)現(xiàn)不同的Receiver處理類
- 將實(shí)現(xiàn)的Receiver組織成鏈?zhǔn)浇Y(jié)構(gòu)
- 客戶端訪問其鏈頭
我們以一個(gè)常用的緩存訪問為例采郎,原始數(shù)據(jù)存放于mysql數(shù)據(jù)庫中,使用key-value的形式緩存于redis與本地內(nèi)存memory狂魔。
查詢時(shí)蒜埋,先查本地內(nèi)存memory,如果查不到最楷,則去redis查詢整份,如果redis也沒有,再到mysql數(shù)據(jù)庫中查籽孙。
定義Handler接口
type Handler func(string) (string, bool)
沒錯(cuò)烈评,就是只有一行代碼!很簡潔有木有犯建。
go語言允許函數(shù)有多個(gè)返回值讲冠,我們用第一個(gè)string類型返查到的值,第二個(gè)bool類型返回是否查詢到值适瓦。
實(shí)現(xiàn)Receiver
由于使用函數(shù)風(fēng)格實(shí)現(xiàn)竿开,其實(shí)就是編寫符合Handler的函數(shù)實(shí)現(xiàn)谱仪。
我們需要提供memory, redis否彩,mysql三個(gè)不同的實(shí)現(xiàn)疯攒。
func MemoryGetValue(key string) (string, bool) {
var value string
var hasValue bool
// try get value from memory cache
return value, hasValue;
}
func RedisGetValue(key string) (string, bool) {
var value string
var hasValue bool
// try get value from redis
return value, hasValue;
}
func MysqlGetValue(key string) (string, bool){
var value string
var hasValue bool
// try get value from mysql database
return value, hasValue;
}
這三個(gè)XXXGetValue函數(shù),與Handler具有相同的函數(shù)簽名列荔,但不需要顯式的聲明其和Handler是等價(jià)的敬尺,這也是go語言的特性之一。
這里略去了具體操作內(nèi)存贴浙、redis砂吞、mysql的代碼,它們不是重點(diǎn)悬而。
將Receiver組織為鏈?zhǔn)浇Y(jié)構(gòu)
func GetValueByChain(key string, functions ...Handler) string{
for _, f := range functions{
value, ok := f(key)
if ok{
return value
}
}
return ""
}
因?yàn)楹瘮?shù)是一等公民呜舒,可以作為參數(shù)傳給另一個(gè)函數(shù)锭汛,所以我們就可以把Handler函數(shù)按處理順序傳參給處理函數(shù)笨奠,變成一個(gè)鏈。
Sender調(diào)用
最后唤殴,Sender調(diào)用GetValueByChain函數(shù)般婆。
func main() {
value := GetValueByChain("key", MemoryGetValue, RedisGetValue, MysqlGetValue)
}
這里本來只有一行代碼。
加了一個(gè)main函數(shù)朵逝,只是為了上面的代碼可以被放放在一個(gè)go文件中并且沒有編譯錯(cuò)誤蔚袍。
使用函數(shù)式的實(shí)現(xiàn),可以結(jié)合語言特性配名,讓編程更輕盈啤咽,更優(yōu)雅。