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