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?
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 :
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. :)
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:
io.Reader
interface with a single method Read
.http.HandlerFunc
, bufio.SplitFunc
Also I find that good names help with design decisions.