函数类型与单方法接口

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

Function types vs single method interfaces

问题

如果一个接口只有一个方法,我应该使用函数类型吗?

以下是两种方法的示例:

type Delegate interface {
  Do(x int) int
}

type App struct {
  delegate Delegate
}

func (this *App) foo() {
  ...
  y := this.delegate.Do(x)
  ...
}

func main() {
  delegate := &DelegateImpl{}
  app := &App{delegate}
  ...
}

测试部分:

type FakeDelegate struct {
  t *testing.T
  expectedX int
  result int
}

func (this *FakeDelegate) Do(x int) int {
  require.Equal(t, this.expectedX, x)
  return this.result
}

func TestAppFoo(t *testing.T) {
  app := &App{}
  app.delegate = &FakeDelegate{t, 1, 2}
  app.foo()
}

看起来右侧的代码(使用函数类型)行数较少,特别是在测试方面。但是难道没有任何陷阱吗?

英文:

If an interface has only a single method, should I use a function type instead?

Here are an examples of both approaches:

type Delegate interface {                 | type Delegate func(x int) int
  Do(x int) int                           | 
}                                         | 
                                          | 
type App struct {                         | type App struct {
  delegate Delegate                       |   delegate Delegate
}                                         | }
                                          | 
func (this *App) foo() {                  | func (this *App) foo() {
  ...                                     |   ...
  y := this.delegate.Do(x)                |   y := this.delegate(x)
  ...                                     |   ...
}                                         | }
                                          | 
func main() {                             | func main() {
  delegate := &DelegateImpl{}             |   delegate := &DelegateImpl{}
  app := &App{delegate}                   |   app := &App{delegate.Process}
  ...                                     |   ...
}                                         | }

Tests:

type FakeDelegate {                       | 
  t *testing.T                            | 
  expectedX int                           | 
  result int                              | 
}                                         | 
                                          | 
func (this *FakeDelegate) Do(x int) int { | 
  require.Equal(t, this.expectedX, x)     | 
  return this.result                      | 
}                                         | 
                                          | 
func TestAppFoo(t *testing.T) {           | func TestAppFoo(t *testing.T) {
  app := &App{}                           |   app := &App{}
  app.delegate = &FakeDelegate{t, 1, 2}   |   app.delegate = func(x int) int {
  app.foo()                               |     require.Equal(t, 1, x)
}                                         |     return 2
                                          |   }
                                          |   app.foo()
                                          | }

It looks like the code on the right (with function type) has less lines, especially when it comes to tests. But isn't there any pitfalls?

答案1

得分: 18

在一般情况下

这是一个设计问题。接口提供了函数所没有的东西:动态分派。因此,如果以后你想要(可能是你自己的)客户端代码将该函数应用于一个对象,并且设想该对象在程序的某个给定点可能是几种不同类型之一,那么就使用接口。

优点:

  • 你可以变得灵活。

缺点:

  • 动态分派会在执行时间上产生一些额外开销。例如,在一个关键循环的中间位置是不希望出现这种情况的。
  • 接口在系统增长时可能会以稍微意外的方式使用。这意味着,虽然大多数情况下你可以轻松地定义一个函数的责任范围,但是接口的责任和类型签名应该经过深思熟虑,作为一个设计决策。
  • 阅读代码时需要经过更多的智力间接性。

Go语言被设计为一种简单而实用的语言。我认为作为愿意使用者,我们应该秉承其哲学。因此,我会说:如果你不需要某样东西,就不要使用它。:)

在你的特定情况下

虽然函数指针确实是一种开发者管理的动态分派形式,但我认为上述的推理仍然适用:选择最简单的解决方案,以满足可预见的需求。否则,Go将变成Java。如果你确信委托永远不需要提供任何其他入口点(例如提供元信息),我建议坚持使用函数指针。

英文:

In the general case

This is a question of design. Interfaces offer something that functions don't : dynamic dispatch. So if later on you want (possibly your own) client code to apply said function to an object, and envision that this object may possibly be of one of several different types at one given point in the program, go for an interface.

The pro : you can get flexible.

The cons :

  • dynamic dispatch costs a minor overhead in execution time. You won't want that in the very middle of a critical loop for instance.
  • interfaces, if available, will be used as the system grows, possibly in slightly unexpected ways. Which means that whereas most times you can define a function's scope of responsiblity light-heartedly enough, an interface's responsibility and type signature should be well thought-out, as a design decision.
  • there are more intellectual indirections to go through for a reader who tries to understand the code.

Go is designed as a simple and pragmatic language. I suppose that as willing users, we should carry forward its philosophy. Hence, I'd say : if you don't need something, don't use it. 函数类型与单方法接口

In your particular case

Although a function pointer is indeed a form of developer-managed dynamic dispatch, it seems to me that the reasoning above remains applicable : go for the simplest solution that could satisfy the foreseeable needs. Otherwise Go will become Java. If you're certain that the delegate will never need to provide any other entry point (like providing meta information for example), I'd say stick to the function pointer.

答案2

得分: 4

我认为使用以下简单规则是合理的:

  • 如果最有可能的实现可以处理未在参数中传递的数据,则使用接口。标准库中的一个例子是io.Reader接口,它有一个名为Read的方法。
  • 否则,使用函数。标准库中的例子有http.HandlerFuncbufio.SplitFunc

此外,我发现良好的命名有助于设计决策。

英文:

I think it is reasonable to use the following simple rule:

  • if the most likely implementation works on data that is not passed in the arguments then use an interface. Example from the standard library: io.Reader interface with a single method Read.
  • otherwise use a function. Examples from the standard library: http.HandlerFunc, bufio.SplitFunc

Also I find that good names help with design decisions.

huangapple
  • 本文由 发表于 2015年8月12日 17:42:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/31961536.html
匿名

发表评论

匿名网友

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

确定