通过 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()
}






