在翻阅golangnet/http
库的实现时,遇到了如下代码:
1 | func Handle(pattern string, handler Handler) { |
第一眼看去,两个函数的样子和所实现的功能类似,区别仅在于函数的签名。其中Handle
函数第二个参数为Handler
接口类型,而HandleFunc
中第二个参数是一个func(ResponseWriter, *Request)
类型。
熟悉net/http
api的朋友应该知道,Handler
接口长这样:
1 | type Handler interface { |
Handler
接口中ServeHTTP
的签名与HandleFunc
中第二个参数的签名一致,都是(ResponseWriter, *Request)
golang这么设计的原因是什么?
带着疑问,我们来看下net/http
包中的HandlerFunc
类型。
1 | type HandlerFunc func(ResponseWriter, *Request) |
既然HandlerFunc
类型实现了ServeHTTP
函数,那么HandlerFunc
肯定就是一个Handler
了。同时HandlerFunc
类型又是一个参数类型为(ResponseWriter, *Request)
的函数。像HandlerFunc
这样的函数就被称为‘接口型函数’。
那么接口型函数有什么作用呢?看下面这个例子。
假如我们有一个有个info类型,包含了一段string类型的信息。并且info实现了函数show
。
1 | type info string |
show
函数的签名与ServeHTTP(w ResponseWriter, r *Request)
相同,仅仅是函数名不同,因此它无法被看做是一个Handler
。
这时我们可以通过HandlerFunc
来强转show
,使它成为一个Handler
,可以将HandlerFunc
看做是一个让函数来满足一个接口的adapter。
1 | http.HandlerFunc(info.show) |
于是我们在把路由和Handler绑定的时候可以这么写。
1 | mux := http.NewServeMux() |
这样一来,可以将一个具有(w ResponseWriter, r *Request)
签名的函数适配到Handler
接口上,实现完整的功能,但是代码总显得不够优雅。如果能把代码中的强转给屏蔽掉的话就更完美了。
此时,回到最初抛出的疑问。在探究Handle
和HandleFunc
函数的实现之后,终于领悟了为什么要这么设计。
1 | func (mux *ServeMux) Handle(pattern string, handler Handler) { |
在HandleFunc
中将HandlerFunc
的强转过程给封装起来了。如果我们使用HandleFunc
来替代Handle
。对于使用者来说无需知道有强转这一步骤,并且不需要实现ServeHTTP(w ResponseWriter, r *Request)
来满足Handler
接口,而是可以使用函数签名相同的函数,使得函数名可以取的更有意义,使代码的组织更为灵活。
1 | mux := http.NewServeMux() |