函数类型与单一方法接口

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?

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.

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.