通过 Golang 的函数类型实现接口
在对 Gin 框架和 Golang net/http 包进行源码分析对比的时候,在 net/http.server 发现了以下让我难以理解的参数传递。作为 Golang 初学者,确实是很难理解这种将函数类型作为接口实现的多态模式。在此处记录一下以防以后踩坑。
背景
http 包中的服务多路复用器(ServeMux)需要确定对于一个特定 URL 来说,应当调用哪个函数来处理它发来的请求。其中有这样一个函数:
1 | func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { |
上述的 mux.Handle()
函数原型是 ServeMux.Handle()
,如下所示:
1 | func (mux *ServeMux) Handle(pattern string, handler Handler) { |
Handler
类型是一个接口:
1 | type Handler interface { |
HandlerFunc
是一个函数类型:
1 | type HandlerFunc func(ResponseWriter, *Request) |
问题
经常写 Python,Java 的我看到 HandleFunc()
函数需要调用 ServeMux.Handle()
,立即就会想到实现一个类,该类实现了 Handler
接口,即拥有一个自己的 ServeHTTP(ResponseWriter, *Request)
成员函数。然后将该类作为参数传入 ServeMux.Handle()
。
1 | // 我想到的写法 |
显然我这种想法在业务层面是不正确的,不应该把处理服务的业务耦合到通信层面的代码上。但我更无法理解的是,在经过 HandlerFunc
类型的类型转换后,HandleFunc()
函数的参数 handler
(这个参数可能是一个有两个类型为 ResponseWriter, *Request
的参数的命名函数、匿名函数或闭包)竟然能够作为接口 Handler
的一个实现传入函数 ServeMux.Handle()
。
再看 HandlerFunc
类型的定义。 HandlerFunc
类型是一个函数,具有和 ServeHTTP()
函数一样的参数。这个类型也拥有一个 ServeHTTP()
函数的实现(这样就实现了 Handler
接口),在这个实现中, HandlerFunc
类型自己调用了自己。
简而言之, HandlerFunc
这个类型的函数实现了接口 Handler
。一个函数能够实现接口,这是我在学习 Go 以前无法想象的,我的刻板印象认为一定只有类才能实现接口。这次的发现让我对于 Go 在面向对象方面与其它语言的不同有了更深的了解。
应用
我们知道了在 Go 中,一个函数能实现接口,但它是如何实际应用的呢?在我学长的一篇博客里有更详细的讲解,我引用其中的内容做出说明:
结构体实现接口:在这个例子中,用了我最熟悉的通过类(
HelloHandler
结构体)实现接口(Handler
接口,它需要实现ServeHTTP()
函数 )的方式,完成了将服务绑定到 URL127.0.0.1:8080/hello
上的功能。运行并访问127.0.0.1:8080/hello
后浏览器会输出 Hello 。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20import "net/http"
type HelloHandler struct{}
func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello"))
}
func RunServer() {
severMux := http.NewServeMux()
helloHandler := &HelloHandler{}
severMux.Handle("/hello", helloHandler)
server := &http.Server{
Addr: "127.0.0.1:8080",
Handler: severMux,
}
server.ListenAndServe()
}函数体实现接口:在该例中,不需要一个实现了
Handler
接口的类传入服务多路复用器,而是直接将业务函数传入severMux.HandleFunc
中。代码更加简洁直观,并且实现了完全相同的功能,在访问127.0.0.1:8080/hello
后仍然能输出 Hello 。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import "net/http"
func sayHello(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello"))
}
func RunServer() {
severMux := http.NewServeMux()
severMux.HandleFunc("/hello", sayHello)
server := &http.Server{
Addr: "127.0.0.1:8080",
Handler: severMux,
}
server.ListenAndServe()
}使用匿名函数后代码能进一步简化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import "net/http"
func RunServer() {
severMux := http.NewServeMux()
severMux.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello"))
})
server := &http.Server{
Addr: "127.0.0.1:8080",
Handler: severMux,
}
server.ListenAndServe()
}