看到一个 Golang 的模式,用一个 function 来实现一个 interface,function 本身就是 interface 的实现。初次看到看了好久才想明白。在这里记录一下。
以 Golang 内置库中的 server.go
1 为例。Handler 的定义如下:
1 2 3 |
type Handler interface { ServeHTTP(ResponseWriter, *Request) } |
如果我们要定义一个 Handler,需要这么写:
1 2 3 4 5 6 7 8 9 |
// 定义一个结构体 type MyHandler struct{} // 实现 http.Handler 接口的方法 func (h MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Hello from MyHandler!") } http.Handle("/hello", MyHandler{}) |
有两个问题:略显啰嗦;距离函数内容最近的 ServeHTTP
是一个 interface 规定的具体的名字,这个函数名字不能变,但是又没有意义,所有的 Handler function 都要写成这个名字。
我们现在写 Golang 显然不是这么写的。我们会这样定义一个 Handler:
1 2 3 4 |
func myHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Hello, World!") } http.Handle("/hello", http.HandlerFunc(myHandler)) |
为什么我们可以这么写呢?因为源代码中有这样几行2:
1 2 3 4 5 6 7 8 9 10 |
// The HandlerFunc type is an adapter to allow the use of // ordinary functions as HTTP handlers. If f is a function // with the appropriate signature, HandlerFunc(f) is a // [Handler] that calls f. type HandlerFunc func(ResponseWriter, *Request) // ServeHTTP calls f(w, r). func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) } |
虽然这里的注释只有短短几行,但是意义深刻。
首先,第一行定义的 type HandlerFunc func(ResponseWriter, *Request)
让我们的 myHanlder
函数变成了一个 type HandlerFunc
类型。
然后,所有的 HandlerFunc
对象都有一个方法,叫做 ServeHTTP
,这就实现了 Handler
这个 interface。实现的内容,就是调用对象本身,对象本身是一个函数,所以就是调用这个函数。
综上,所有符合 ServeHTTP(w ResponseWriter, r *Request)
签名的函数都可以转换成 HandlerFunc 对象,(虽然它是函数,但是函数也是对象。)即所有签名如此的函数,都可以是一个 Handler 了。
我们就可以这么写:
1 2 3 4 |
func myHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Hello, World!") } http.Handle("/hello", http.HandlerFunc(myHandler)) |
那么为什么不直接把 Handler 定义成一个函数呢?
1 |
type Handler func(ResponseWriter, *Request) |
就可以实现一样的效果了。
这是因为,Handler 可以变得很复杂,比如,Golang 的 middleware 本质上就是基于 Handler 的链式调用来实现的。复杂的 Handler 需要维护一些内部的状态,这种情况下,struct
就比 function 好用很多了。比如 httpauth3 这个库,就先初始化成 Handler 再使用。
那如果还是把 Handler 定义成一个 function,三方库规定在使用的时候,先初始化一个三方库定义的对象,然后三方库提供兼容 Handler 的函数,好像能达到一样的效果?
这样的话,多个 middleware 的入参和返回是不一样的对象,就无法串起来了。而如果把 Handler 定义成一个标准库里面的对象,就可以做到:middleware 接收的是一个 Handler,返回的还是一个 Handler4。只要 middleware 是这样的接口,它们就可以串联使用。
还有一个有趣的一点,Golang 里面不光函数可以实现 interface,任何类型都可以5。(Golang 还真是一切皆对象呢。)
1 2 3 4 5 6 7 8 9 10 11 12 13 |
type Counter uint64 func (i *Counter) ServeHTTP(w http.ResponseWriter, r *http.Request) { *i++ response := fmt.Sprintf("%v", *i) w.Write([]byte(response)) } func main() { var c Counter http.Handle("/count", &c) http.ListenAndServe(":8080", nil) } |
- https://cs.opensource.google/go/go/+/refs/tags/go1.24.1:src/net/http/server.go;l=88 ↩︎
- https://cs.opensource.google/go/go/+/refs/tags/go1.24.1:src/net/http/server.go;l=2290 ↩︎
- https://github.com/goji/httpauth?tab=readme-ov-file#nethttp ↩︎
- https://github.com/goji/httpauth/blob/master/basic_auth.go#L153 ↩︎
- I read it from here: Functions implementing interfaces in go | Karthik Karanth ↩︎
文章提到的代码肯定编译不过:
> func myHandler(w http.ResponseWriter, r *http.Request) {
> fmt.Fprintln(w, “Hello, World!”)
> }
> http.Handle(“/hello”, myHandler)
>
> 为什么我们可以这么写呢?
应该是 http.HandleFunc。
>其中,HandlerFunc(…) 是一个显式类型转换,可以省略。
省略不了。
不过这确实是golang我最喜欢的特性。
> 不过这确实是golang我最喜欢的特性。
指的是必须类型转换,还是 one-function interface?
指的是万物皆可实现interface
谢谢,已经改正,如 https://www.kawabangga.com/posts/6903#comment-51146 说的,我以为这个类型转换可以省略呢,其实不行。