在Go语言中,为什么要在函数上实现接口?

huangapple go评论77阅读模式
英文:

Why would you implement an interface on a function in Go?

问题

我正在阅读关于Go语言中函数如何实现接口的内容(例如这个例子这个例子)。

但是,函数实现接口有什么价值呢?

例如,为什么要花时间定义一个接口,为函数定义一个新类型,并定义一个方法,像这样:

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

type HandlerFunc func(*Conn, *Request)

func (f HandlerFunc) ServeHTTP(c *Conn, req *Request) {
    f(c, req)
}

而不是像这样只创建一个函数:

func ServeHTTP(f func(*Conn, *Request), c *Conn, req *Request) {
    f(c, req)
}

提前感谢!

英文:

I was reading that, in Go, functions can implement interfaces (like in this example or this example).

But what value is there in having a function implement an interface?

For example, why take the time to define an interface, a new type for the function, and a method, like this:

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

type HandlerFunc func(*Conn, *Request)

func (f HandlerFunc) ServeHTTP(c *Conn, req *Request) {
    f(c, req)
}

when instead you could just create a single function like this:

func ServeHTTP(f func(*Conn, *Request), c *Conn, req *Request) {
    f(c, req)
}

Thanks in advance!

答案1

得分: 10

一个单独的函数(在你的例子中是func ServeHTTP)不能满足任何接口。

将方法附加到函数类型的原因是为了满足接口的要求。

这样做有什么价值呢?与任何其他类型实现接口的价值相同。让我们看一下http.Handler接口,因为你提到了它。

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

你可以通过一个结构体来实现这个接口,例如:

type myHanlder struct {
   /* 一些字段 */
}

func (s *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    /* 做一些事情 */
}

你也可以使用一些更简单的类型来满足它。比如,一个字符串:

type myStringHandler string

func (h myStringHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    _, _ = fmt.Fprintf(w, "The string is: %s", h)
}

但是,如果你想直接实现函数,就像这样:

func myHandler(w http.ResponseWriter, r *http.Request) {
    /* 做一些事情 */
}

这并不能满足接口的要求。所以我们需要创建一个与函数签名匹配并提供接口方法的类型。这就是http.HandlerFunc的作用:

type HandlerFunc func(ResponseWriter, *Request)

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

如果问题是:为什么标准库的作者决定将ServerHTTP设计为接口而不是函数类型,而不是搜索旧的邮件列表来寻找线索,只能进行推测,这与本主题无关,但是我可以稍微猜测一下:

  • 接口更容易使用,因为它们可以轻松地进行扩展/嵌入。也就是说,我可以实现一个既满足http.Handler接口又满足其他接口的结构体。如果http.Handler不是一个接口,这将更加麻烦(尽管不是完全不可能)。
  • 这是惯用的做法。在Go中,使用接口进行“鸭子类型”正好符合Go的做事方式。也许这有点循环论证,因为它符合Go的方式,所以才这样做。
  • 如果你有一个复杂的处理程序,也许需要跟踪自己的状态,使用结构体是自然的解决方案,将其公开为接口比闭包更自然(如果使用函数类型实现,将需要闭包)。

作为一个思维实验,想象一下如果io.Reader接口是一个函数类型:

type Reader func(p []byte) (n int, err error)

我认为很容易看出这将导致混乱。

英文:

A single function (in your example, func ServeHTTP, doesn't satisfy any interfaces.

The reason to attach a method to a function type is therefore to satisfy an interface.

What value is there in this? The same value in having any other type implement an interface. Let's look at the http.Handler interface, since you brought that up.

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

You can implement this interface with, for example, a struct:

type myHanlder struct {
   /* some fields */
}

func (s *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    /* do things */
}

You could also satisfy it with some simpler type. Say, a string:

type myStringHandler string

func (h myStringHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    _, _ = fmt.Fprintf(w, "The string is: %s", h)
}

But if you want to just implement the function directly, as with:

func myHandler(w http.ResponseWriter, r *http.Request) {
    /* do things */
}

This doesn't satisfy the interface. So we need to create a type that matches the function signature, and provides the interface methods. That's what http.HandlerFunc does:

type HandlerFunc func(ResponseWriter, *Request)

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

<hr>

If the question is: Why did the authors of the standard library decide to make ServerHTTP an interface, rather than a function type, without searching old mailing list archives for clues, only speculation is possible, which is really off-topic here, but here's a small stab at it:

  • Interfaces are easier to work with, because they can be easily expanded/embedded. That is, I could implement a struct that satisfies both the http.Handler interface, and some other interface. This would be more cumbersome (though not strictly impossible) if http.Handler wasn't an interface.
  • It's idiomatic. Using an interface for "duck typing" just fits with the Go way of doing things. Maybe that's a bit of a tautology, since the reason it fits with Go is becuase it's done this way.
  • If you ever have a complex handler, which maybe tracks its own state, using a struct is the natural solution, and exposing it as an interface is more natural than a closure (which would be required if this were implemented as a function type).

As a thought experiment, just imagine if the io.Reader interface where instead a function type:

type Reader func(p []byte) (n int, err error)

I think it's pretty easy to see that madness would ensue all over the place.

huangapple
  • 本文由 发表于 2021年7月17日 13:48:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/68417772.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定