在对 Gin 框架和 Golang net/http 包进行源码分析对比的时候,在 net/http.server 发现了以下让我难以理解的参数传递。作为 Golang 初学者,确实是很难理解这种将函数类型作为接口实现的多态模式。在此处记录一下以防以后踩坑。

背景

http 包中的服务多路复用器(ServeMux)需要确定对于一个特定 URL 来说,应当调用哪个函数来处理它发来的请求。其中有这样一个函数:

1
2
3
4
5
6
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler)) // 请注意此行
}

上述的 mux.Handle() 函数原型是 ServeMux.Handle() ,如下所示:

1
2
3
4
func (mux *ServeMux) Handle(pattern string, handler Handler) {
...
}

Handler 类型是一个接口:

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

HandlerFunc 是一个函数类型:

1
2
3
4
5
6
type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}

问题

经常写 Python,Java 的我看到 HandleFunc() 函数需要调用 ServeMux.Handle() ,立即就会想到实现一个类,该类实现了 Handler 接口,即拥有一个自己的 ServeHTTP(ResponseWriter, *Request) 成员函数。然后将该类作为参数传入 ServeMux.Handle()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 我想到的写法

// MyClass 实现了 Handler 接口
type MyClass struct{
ServeHTTP(ResponseWriter, *Request)
}

func (c *MyClass) ServeHTTP(ResponseWriter, *Request){
...
}

func (mux *ServeMux) HandleFunc(pattern string, ...) {
...
mux.Handle(pattern, new(MyClass)) // 请注意此行
}

显然我这种想法在业务层面是不正确的,不应该把处理服务的业务耦合到通信层面的代码上。但我更无法理解的是,在经过 HandlerFunc 类型的类型转换后,HandleFunc() 函数的参数 handler (这个参数可能是一个有两个类型为 ResponseWriter, *Request 的参数的命名函数、匿名函数或闭包)竟然能够作为接口 Handler 的一个实现传入函数 ServeMux.Handle()

再看 HandlerFunc 类型的定义。 HandlerFunc 类型是一个函数,具有和 ServeHTTP() 函数一样的参数。这个类型也拥有一个 ServeHTTP() 函数的实现(这样就实现了 Handler 接口),在这个实现中, HandlerFunc 类型自己调用了自己。

简而言之, HandlerFunc 这个类型的函数实现了接口 Handler。一个函数能够实现接口,这是我在学习 Go 以前无法想象的,我的刻板印象认为一定只有类才能实现接口。这次的发现让我对于 Go 在面向对象方面与其它语言的不同有了更深的了解。

应用

我们知道了在 Go 中,一个函数能实现接口,但它是如何实际应用的呢?在我学长的一篇博客里有更详细的讲解,我引用其中的内容做出说明:

  • 结构体实现接口:在这个例子中,用了我最熟悉的通过类( HelloHandler 结构体)实现接口( Handler接口,它需要实现 ServeHTTP() 函数 )的方式,完成了将服务绑定到 URL 127.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
    20
    import "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
    17
    import "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
    16
    import "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()
    }