【问题标题】:golang; trouble understanding a function as a receiver戈朗;难以理解作为接收器的功能
【发布时间】:2020-02-07 23:06:51
【问题描述】:

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

他们这样称呼http.Handle

func init() {
    http.Handle("/view", appHandler(viewRecord))
}

http.Handle 的第二个参数需要一个类型Handler (https://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)
    }
}

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

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

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

appHandler(viewRecord) 也做演员吗?

基本上,我正在寻找关于函数成为接收器的含义的一些明确性。我是新手,我想我在这里不小心落在了一个不平凡的地雷上。

【问题讨论】:

  • 对于新手:了解这里发生的事情有 2 个先决条件1) 阅读OP's first link 中的“简化重复错误处理”部分。 2) 查看 Go 的 net/http 包中的一些源代码,尤其是 HandlerFunc 类型和 Handler 接口,以充分理解链接中的上述部分。

标签: go


【解决方案1】:

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

type X int

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

func (x X) method() {
  // Do something with x
}

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

type F func()

这里,F 是一个新类型,因此你可以为它定义方法:

func (x F) method() {
   x()
}

通过上面的声明,如果value 的类型是F,现在您可以调用value.method()

a:=F(func() {fmt.Println("hey")})
a.method()

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

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

type appHandler func(http.ResponseWriter, *http.Request)

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

func myHandler(http.ResponseWriter, *http.Request) {
  // Handle request
}

您可以在任何需要appHandler 的地方传递此函数。但是,如果不编写这样的结构,则无法将 if 传递到需要 Handler 的位置:

type myHandlerStruct struct{}

func (myHandlerStruct) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   myHandler(w,r)
}

您可以为appHandler 类型定义一个方法,而不是定义一个新结构:

func (a appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   a(w,r)
}

现在您可以将appHandler 传递到需要appHandler 的位置以及需要Handler 的位置。如果它被调用为HandlerServeHTTP 方法将简单地将调用转发到底层函数。

【讨论】:

  • 我明白你的意思,但我认为你没有阅读 OP 链接的文章。 (我知道,我们很忙 :) 您的示例本身并没有错,但它可能会令人困惑,因为它与 OP 正在谈论的链接不太相关。
【解决方案2】:

那里有很多问题,但我会尽力解决所有问题。

这在appHandler 类型上定义了一个方法ServeHTTP,这意味着appHandler 现在是一个有效的net/http.HandlerappHandler 类型恰好是一个函数类型,所以是的 - 这里有一个函数值,其中包含可以调用的方法,与调用函数本身分开。这已经是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)
    }
}

所以,在函数的第一行,ServeHTTP 调用了 appHandler 函数 - fn appHandler 是我们的接收者,使 fn 成为函数值,我们可以调用它:

fn(w, r)

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

http.Handle("/view", appHandler(viewRecord))

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

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)
        }
    }
}

这完全一样,但使用闭包和函数参数而不是方法和函数接收器。这是通过调用我们的包装函数来使用的:

http.Handle("/view", http.HandlerFunc(middleware(viewRecord)))

这使用我们的 middleware 函数来包装 viewRecord 并将其转换为 http.HandlerFunc (如前所述,这实际上将执行与 appHandler 使用函数类型方法所做的相同的事情。

【讨论】:

  • 好的,最后一个例子对我有很大帮助。 middleware 函数正是我会这样做的方式,来自功能背景。这意味着即使作为一个初学者去程序员,编写你称之为中间件的东西,它接受不合格的函数(viewRecord)并产生一个合格的函数是我会做的。所以这里有趣的是这个“风格”和这个“接收者”符号之间的转换。我会更多地研究这个,我仍然觉得我没有掌握这个符号。
  • 当您说“调用它”时,您的意思是“调用它”吗?比如,func (fn somefunctype) y() 表示y() 可以调用fn()?如y() {fn()}?这似乎仍然与 func (p person) talk() 不同,然后您执行 someperson.talk()。 “调用顺序”是相反的。
  • 在你的最后一个例子中,Handler 的接口是如何得到满足的,因为它没有一个名为ServeHTTP 的函数? Handle不是会尝试调用middleware(viewRecord).ServeHTTP吗,但那不存在?
  • 我不确定你的意思 - “调用它”没有出现在我的答案中。 func (fn somefunctype) y() 表示方法y 的接收者是fn,所以如果fn 是一个函数,它可以调用它,就像它是一个普通的函数参数一样。接收器只是常规函数参数,它们也只是应用语法来调用函数作为接收器类型值的方法。
  • 在我的最后一个示例中,我使用了http.HandlerFunc(起初我将其拼写为HandleFunc,抱歉)。正如我在答案末尾指出的那样,http.HandlerFunc 的工作方式与您的问题的 appHandler 一样 - 它是一个命名函数类型,它定义了一个 ServeHTTP 方法:golang.org/pkg/net/http/#HandlerFunc
猜你喜欢
  • 2018-09-04
  • 1970-01-01
  • 2015-05-19
  • 1970-01-01
  • 2022-11-26
  • 1970-01-01
  • 2014-12-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多