golang接口型函数及其应用

在翻阅golangnet/http库的实现时,遇到了如下代码:

1
2
3
4
5
6
7
func Handle(pattern string, handler Handler) {
DefaultServeMux.Handle(pattern, handler)
}

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}

第一眼看去,两个函数的样子和所实现的功能类似,区别仅在于函数的签名。其中Handle函数第二个参数为Handler接口类型,而HandleFunc中第二个参数是一个func(ResponseWriter, *Request)类型。

熟悉net/httpapi的朋友应该知道,Handler接口长这样:

1
2
3
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}

Handler接口中ServeHTTP的签名与HandleFunc中第二个参数的签名一致,都是(ResponseWriter, *Request)

wtf1

golang这么设计的原因是什么?

带着疑问,我们来看下net/http包中的HandlerFunc类型。

1
2
3
4
5
type HandlerFunc func(ResponseWriter, *Request)

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}

既然HandlerFunc类型实现了ServeHTTP函数,那么HandlerFunc肯定就是一个Handler了。同时HandlerFunc类型又是一个参数类型为(ResponseWriter, *Request)的函数。像HandlerFunc这样的函数就被称为‘接口型函数’。
那么接口型函数有什么作用呢?看下面这个例子。

假如我们有一个有个info类型,包含了一段string类型的信息。并且info实现了函数show

1
2
3
4
5
type info string

func (i info) show(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "info: %s", i)
}

show函数的签名与ServeHTTP(w ResponseWriter, r *Request)相同,仅仅是函数名不同,因此它无法被看做是一个Handler

这时我们可以通过HandlerFunc来强转show,使它成为一个Handler,可以将HandlerFunc看做是一个让函数来满足一个接口的adapter。

1
http.HandlerFunc(info.show)

于是我们在把路由和Handler绑定的时候可以这么写。

1
2
3
mux := http.NewServeMux()
info := info("some info")
mux.Handle("/info1", http.HandlerFunc(info.show))

这样一来,可以将一个具有(w ResponseWriter, r *Request)签名的函数适配到Handler接口上,实现完整的功能,但是代码总显得不够优雅。如果能把代码中的强转给屏蔽掉的话就更完美了。

此时,回到最初抛出的疑问。在探究HandleHandleFunc函数的实现之后,终于领悟了为什么要这么设计。

1
2
3
4
5
6
7
8
func (mux *ServeMux) Handle(pattern string, handler Handler) {
some code ...
}

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
some code ...
mux.Handle(pattern, HandlerFunc(handler))
}

HandleFunc中将HandlerFunc的强转过程给封装起来了。如果我们使用HandleFunc来替代Handle。对于使用者来说无需知道有强转这一步骤,并且不需要实现ServeHTTP(w ResponseWriter, r *Request)来满足Handler接口,而是可以使用函数签名相同的函数,使得函数名可以取的更有意义,使代码的组织更为灵活。

1
2
3
mux := http.NewServeMux()
info := info("some info")
mux.HandleFunc("/info2", info.show)