Golang 中的 One-function Interfaces

看到一个 Golang 的模式,用一个 function 来实现一个 interface,function 本身就是 interface 的实现。初次看到看了好久才想明白。在这里记录一下。

以 Golang 内置库中的 server.go1 为例。Handler 的定义如下:

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

如果我们要定义一个 Handler,需要这么写:

// 定义一个结构体
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:

func myHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "Hello, World!")
}
http.Handle("/hello", http.HandlerFunc(myHandler))

为什么我们可以这么写呢?因为源代码中有这样几行2

// 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 了。

我们就可以这么写:

func myHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "Hello, World!")
}
http.Handle("/hello", http.HandlerFunc(myHandler))

那么为什么不直接把 Handler 定义成一个函数呢?

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 还真是一切皆对象呢。)

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)
}
  1. https://cs.opensource.google/go/go/+/refs/tags/go1.24.1:src/net/http/server.go;l=88 ↩︎
  2. https://cs.opensource.google/go/go/+/refs/tags/go1.24.1:src/net/http/server.go;l=2290 ↩︎
  3. https://github.com/goji/httpauth?tab=readme-ov-file#nethttp ↩︎
  4. https://github.com/goji/httpauth/blob/master/basic_auth.go#L153 ↩︎
  5. I read it from here: Functions implementing interfaces in go | Karthik Karanth ↩︎


Golang 中的 One-function Interfaces”已经有6条评论

  1. 文章提到的代码肯定编译不过:

    > func myHandler(w http.ResponseWriter, r *http.Request) {
    > fmt.Fprintln(w, “Hello, World!”)
    > }
    > http.Handle(“/hello”, myHandler)
    >
    > 为什么我们可以这么写呢?

    应该是 http.HandleFunc。

回复 Frost 取消回复

您的邮箱地址不会被公开。 必填项已用 * 标注