戈朗; 无法将函数理解为接收器

Tom*_*mmy 5 go

我正在尝试通读以下内容:https : //blog.golang.org/error-handling-and-go特别是标题为Simplifying repetitive error handling.

他们这样称呼http.Handle

func init() {
    http.Handle("/view", appHandler(viewRecord))
}
Run Code Online (Sandbox Code Playgroud)

http.Handle的第二个参数需要一个类型Handlerhttps://golang.org/pkg/net/http/#Handler),它需要一个方法serveHttp

serveHttp这里的功能:

func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    if err := fn(w, r); err != nil {
        http.Error(w, err.Error(), 500)
    }
}
Run Code Online (Sandbox Code Playgroud)

所以,他们的类型appHandler现在实现了 Handler 接口,因为它实现了ServeHTTP,我明白了。所以它可以在Handle函数中使用而viewRecord不能。

我感到困惑的地方是viewRecordwhich 类型appHandlerServeHTTP. 哪个调用哪个?他们对“函数如何也可以成为接收器”进行了附加评论,我认为这就是我被绊倒的地方。

在这里,fn appHandler作为接收者,我希望有类似的东西viewRecord.serveHTTP(),但这没有意义,而且viewRecord是一个函数。我认为正在发生的是Handle函数调用serveHTTP,但如何serveHTTP调用viewRecord

appHandler(viewRecord)做演员吗?

基本上,我正在寻找关于函数作为接收器意味着什么的一些清晰度。我是新手,我想我不小心落在了这里的一个非平凡的地雷上。

Bur*_*dar 5

任何类型都可以是接收器。例如:

type X int
Run Code Online (Sandbox Code Playgroud)

X是一个新类型,您可以为它创建方法:

func (x X) method() {
  // Do something with x
}
Run Code Online (Sandbox Code Playgroud)

在 Go 中,函数就像任何其他类型一样。所以如果你有一个函数类型:

type F func()
Run Code Online (Sandbox Code Playgroud)

F是一个新类型,因此您可以为其定义方法:

func (x F) method() {
   x()
}
Run Code Online (Sandbox Code Playgroud)

有了上面的声明,现在你可以调用value.method()ifvalue类型了F

a:=F(func() {fmt.Println("hey")})
a.method()
Run Code Online (Sandbox Code Playgroud)

这里,a是一个类型的变量FF有一个方法叫做method,所以你可以调用a.method. 当你调用它时,a.method调用a,这是一个函数。

回到你的例子,appHandler似乎是一个函数类型:

type appHandler func(http.ResponseWriter, *http.Request)
Run Code Online (Sandbox Code Playgroud)

因此,可以使用具有该签名的任何函数来代替appHandler. 假设您编写了这样一个函数:

func myHandler(http.ResponseWriter, *http.Request) {
  // Handle request
}
Run Code Online (Sandbox Code Playgroud)

您可以在任何需要 an 的地方传递此函数appHandler。但是,如果Handler不编写这样的结构,就不能将 if 传递到需要 a 的地方:

type myHandlerStruct struct{}

func (myHandlerStruct) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   myHandler(w,r)
}
Run Code Online (Sandbox Code Playgroud)

您可以为 type 定义一个方法,而不是定义一个新的结构appHandler

func (a appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   a(w,r)
}
Run Code Online (Sandbox Code Playgroud)

现在您可以传递appHandlerappHandler需要的地方以及Handler需要的地方。如果它作为 a 调用Handler,则该ServeHTTP方法将简单地将调用转发到底层函数。


Adr*_*ian 4

好吧,有很多问题,但我会尽力回答所有问题。

ServeHTTP这定义了该类型的方法appHandler,这意味着该方法appHandler现在是有效的net/http.Handler。该类型appHandler恰好是函数类型,所以是的 - 这里有一个函数值,其中包含可以调用它的方法,与调用函数本身分开。http.HandleFunc顺便说一句,这已经是标准库中的工作原理- 检查其源代码也可能有助于理解其工作原理。

注册处理程序后,net/http调用其ServeHTTP方法来处理传入请求。我们类型的方法appHandler是:

func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    if err := fn(w, r); err != nil {
        http.Error(w, err.Error(), 500)
    }
}
Run Code Online (Sandbox Code Playgroud)

因此,在函数的第一行,ServeHTTP调用appHandler函数 -fn appHandler是我们的接收者,创建fn一个函数值,我们可以调用它:

fn(w, r)
Run Code Online (Sandbox Code Playgroud)

这是通过将我们的处理函数转换为以下appHandler类型来利用的:

http.Handle("/view", appHandler(viewRecord))
Run Code Online (Sandbox Code Playgroud)

这实际上与更常见的中间件模式没有什么不同:

func middleware(fn func(w http.ResponseWriter, r *http.Request) error) func(w http.ResponseWriter, r *http.Request) {
    return func(w http.ResponseWriter, r *http.Request) {
        if err := fn(w, r); err != nil {
            http.Error(w, err.Error(), 500)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这做了完全相同的事情,但是使用闭包和函数参数而不是方法和函数接收器。这是通过调用我们的包装函数来利用的:

http.Handle("/view", http.HandlerFunc(middleware(viewRecord)))
Run Code Online (Sandbox Code Playgroud)

这使用我们的middleware函数来包装viewRecord并将其转换为 a http.HandlerFunc(正如前面提到的,它实际上会做appHandler与函数类型方法相同的事情)。