Golang 中的 One-function Interfaces

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

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

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

有两个问题:略显啰嗦;距离函数内容最近的 ServeHTTP 是一个 interface 规定的具体的名字,这个函数名字不能变,但是又没有意义,所有的 Handler function 都要写成这个名字。

我们现在写 Golang 显然不是这么写的。我们会这样定义一个 Handler:

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

虽然这里的注释只有短短几行,但是意义深刻。

首先,第一行定义的 type HandlerFunc func(ResponseWriter, *Request) 让我们的 myHanlder 函数变成了一个 type HandlerFunc 类型。

然后,所有的 HandlerFunc 对象都有一个方法,叫做 ServeHTTP,这就实现了 Handler 这个 interface。实现的内容,就是调用对象本身,对象本身是一个函数,所以就是调用这个函数。

综上,所有符合 ServeHTTP(w ResponseWriter, r *Request) 签名的函数都可以转换成 HandlerFunc 对象,(虽然它是函数,但是函数也是对象。)即所有签名如此的函数,都可以是一个 Handler 了。

我们就可以这么写:

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

就可以实现一样的效果了。

这是因为,Handler 可以变得很复杂,比如,Golang 的 middleware 本质上就是基于 Handler 的链式调用来实现的。复杂的 Handler 需要维护一些内部的状态,这种情况下,struct 就比 function 好用很多了。比如 httpauth3 这个库,就先初始化成 Handler 再使用。

那如果还是把 Handler 定义成一个 function,三方库规定在使用的时候,先初始化一个三方库定义的对象,然后三方库提供兼容 Handler 的函数,好像能达到一样的效果?

这样的话,多个 middleware 的入参和返回是不一样的对象,就无法串起来了。而如果把 Handler 定义成一个标准库里面的对象,就可以做到:middleware 接收的是一个 Handler,返回的还是一个 Handler4。只要 middleware 是这样的接口,它们就可以串联使用。

还有一个有趣的一点,Golang 里面不光函数可以实现 interface,任何类型都可以5。(Golang 还真是一切皆对象呢。)

  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”已经有5条评论

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

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

    应该是 http.HandleFunc。

Leave a comment

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