滄州哪里可以做網(wǎng)站中山seo排名
文章目錄
- 問(wèn)題
- 價(jià)值
- 使用場(chǎng)景
- 其他語(yǔ)言類(lèi)似特性
問(wèn)題
在 動(dòng)手寫(xiě)分布式緩存 - GeeCache day2 單機(jī)并發(fā)緩存 這篇文章中,有一個(gè)接口型函數(shù)的實(shí)現(xiàn):
// A Getter loads data for a key.
type Getter interface {Get(key string) ([]byte, error)
}// A GetterFunc implements Getter with a function.
type GetterFunc func(key string) ([]byte, error)// Get implements Getter interface function
func (f GetterFunc) Get(key string) ([]byte, error) {return f(key)
}
這里呢,定義了一個(gè)接口 Getter
,只包含一個(gè)方法 Get(key string) ([]byte, error)
,緊接著定義了一個(gè)函數(shù)類(lèi)型 GetterFunc
,GetterFunc
參數(shù)和返回值與 Getter
中 Get
方法是一致的。而且 GetterFunc
還定義了 Get
方式,并在 Get
方法中調(diào)用自己,這樣就實(shí)現(xiàn)了接口 Getter
。所以 GetterFunc 是一個(gè)實(shí)現(xiàn)了接口的函數(shù)類(lèi)型,簡(jiǎn)稱(chēng)為接口型函數(shù)。
接口型函數(shù)只能應(yīng)用于接口內(nèi)部只定義了一個(gè)方法的情況
,例如接口 Getter
內(nèi)部有且只有一個(gè)方法 Get
。既然只有一個(gè)方法,為什么還要多此一舉,封裝為一個(gè)接口呢?定義參數(shù)的時(shí)候,直接用 GetterFunc
這個(gè)函數(shù)類(lèi)型不就好了,讓用戶(hù)直接傳入一個(gè)函數(shù)作為參數(shù),不更簡(jiǎn)單嗎?
所以呢,接口型函數(shù)的價(jià)值什么?
價(jià)值
我們想象這么一個(gè)使用場(chǎng)景,GetFromSource
的作用是從某數(shù)據(jù)源獲取結(jié)果,接口類(lèi)型 Getter
是其中一個(gè)參數(shù),代表某數(shù)據(jù)源:
func GetFromSource(getter Getter, key string) []byte {buf, err := getter.Get(key)if err == nil {return buf}return nil
}
我們可以有多種方式調(diào)用該函數(shù):
方式一:GetterFunc 類(lèi)型的函數(shù)作為參數(shù)
GetFromSource(GetterFunc(func(key string) ([]byte, error) {return []byte(key), nil
}), "hello")
支持匿名函數(shù),也支持普通的函數(shù):
func test(key string) ([]byte, error) {return []byte(key), nil
}func main() {GetFromSource(GetterFunc(test), "hello")
}
將 test
強(qiáng)制類(lèi)型轉(zhuǎn)換為 GetterFunc
,GetterFunc
實(shí)現(xiàn)了接口 Getter
,是一個(gè)合法參數(shù)。這種方式適用于邏輯較為簡(jiǎn)單的場(chǎng)景。
方式二:實(shí)現(xiàn)了 Getter 接口的結(jié)構(gòu)體作為參數(shù)
type DB struct{ url string}func (db *DB) Query(sql string, args ...string) string {// ...return "hello"
}func (db *DB) Get(key string) ([]byte, error) {// ...v := db.Query("SELECT NAME FROM TABLE WHEN NAME= ?", key)return []byte(v), nil
}func main() {GetFromSource(new(DB), "hello")
}
DB
實(shí)現(xiàn)了接口 Getter
,也是一個(gè)合法參數(shù)。這種方式適用于邏輯較為復(fù)雜的場(chǎng)景,如果對(duì)數(shù)據(jù)庫(kù)的操作需要很多信息,地址、用戶(hù)名、密碼,還有很多中間狀態(tài)需要保持,比如超時(shí)、重連、加鎖等等。這種情況下,更適合封裝為一個(gè)結(jié)構(gòu)體作為參數(shù)。
這樣,既能夠?qū)⑵胀ǖ暮瘮?shù)類(lèi)型(需類(lèi)型轉(zhuǎn)換)作為參數(shù),也可以將結(jié)構(gòu)體作為參數(shù),使用更為靈活,可讀性也更好,這就是接口型函數(shù)的價(jià)值。
使用場(chǎng)景
這個(gè)特性在 groupcache
等大量的 Go
語(yǔ)言開(kāi)源項(xiàng)目中被廣泛使用,標(biāo)準(zhǔn)庫(kù)中用得也不少,net/http
的 Handler
和 HandlerFunc
就是一個(gè)典型。
我們先看一下 Handler
的定義:
type Handler interface {ServeHTTP(ResponseWriter, *Request)
}
type HandlerFunc func(ResponseWriter, *Request)func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {f(w, r)
}
摘自 Go
語(yǔ)言源代碼 net/http/server.go
我們可以 http.Handle
來(lái)映射請(qǐng)求路徑和處理函數(shù),Handle
的定義如下:
func Handle(pattern string, handler Handler)
第二個(gè)參數(shù)是即接口類(lèi)型 Handler
,我們可以這么用。
func home(w http.ResponseWriter, r *http.Request) {w.WriteHeader(http.StatusOK)_, _ = w.Write([]byte("hello, index page"))
}func main() {http.Handle("/home", http.HandlerFunc(home))_ = http.ListenAndServe("localhost:8000", nil)
}
通常,我們還會(huì)使用另外一個(gè)函數(shù) http.HandleFunc
,HandleFunc
的定義如下:
func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
第二個(gè)參數(shù)是一個(gè)普通的函數(shù)類(lèi)型,那可以直接將 home
傳遞給 HandleFunc
:
func main() {http.HandleFunc("/home", home)_ = http.ListenAndServe("localhost:8000", nil)
}
那如果我們看過(guò) HandleFunc
的內(nèi)部實(shí)現(xiàn)的話(huà),就會(huì)知道兩種寫(xiě)法是完全等價(jià)的,內(nèi)部將第二種寫(xiě)法轉(zhuǎn)換為了第一種寫(xiě)法。
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {if handler == nil {panic("http: nil handler")}mux.Handle(pattern, HandlerFunc(handler))
}
如果你仔細(xì)觀察,會(huì)發(fā)現(xiàn) http.ListenAndServe
的第二個(gè)參數(shù)也是接口類(lèi)型 Handler
,我們使用了標(biāo)準(zhǔn)庫(kù) net/http
內(nèi)置的路由,因此呢,傳入的值是 nil
。那如果這個(gè)地方我們傳入的是一個(gè)實(shí)現(xiàn)了 Handler
接口的結(jié)構(gòu)體呢?就可以完全托管所有的 HTTP
請(qǐng)求,后續(xù)怎么路由,怎么處理,請(qǐng)求前后增加什么功能,都可以自定義了。慢慢地,就變成了一個(gè)功能豐富的 Web
框架了。如果你感興趣呢,可以閱讀 7天用Go從零實(shí)現(xiàn)Web框架Gee教程。
其他語(yǔ)言類(lèi)似特性
如果有 Java
編程經(jīng)驗(yàn)的同學(xué)可能比較有感觸。Java 1.5
中是不支持直接傳入函數(shù)的,參數(shù)要么是接口,要么是對(duì)象。舉一個(gè)最簡(jiǎn)單的例子,列表自定義排序時(shí),需要實(shí)現(xiàn)一個(gè)匿名的 Comparator
類(lèi),重寫(xiě) compare
方法。
Collections.sort(list, new Comparator<Integer>(){@Overridepublic int compare(Integer o1, Integer o2) {return o2 - o1;}
});
Java 1.8
中引入了大量的函數(shù)式編程的特性,其中 lambda
表達(dá)式和函數(shù)式接口就是一個(gè)很好的簡(jiǎn)化 Java
寫(xiě)法的特性。Java 1.8
中,上述的例子可以簡(jiǎn)化為:
Collections.sort(list, (Integer o1, Integer o2) -> o2 - o1 );
即從需要構(gòu)造一個(gè)匿名對(duì)象簡(jiǎn)化為只需要一個(gè)lambda
函數(shù)表達(dá)式,可以認(rèn)為是面向?qū)ο笈c函數(shù)式編程的一種結(jié)合。同樣地,這種寫(xiě)法只支持只定義了一個(gè)方法的接口類(lèi)型,因?yàn)橹欢x了一個(gè)方法的接口,就會(huì)很明確傳入進(jìn)來(lái)的函數(shù)對(duì)應(yīng)接口中的哪個(gè)方法。
正是這種結(jié)合,可以達(dá)到實(shí)現(xiàn)相同代碼,代碼量更少的目的。