Reputation: 1728
Given the following types :
type A struct {
...
}
func (a *A) Process() {
...
}
I would like to pass the method Process
of the type A
to another function and be able to access the content of the underlying instance of A
.
How should I pass the method to another function? Via a pointer? And how should it be called ?
The Process()
method won't modify the instance of A
, I am using a pointer on the method receiver because the struct is quite large. The idea behind my question is to avoid declaring the function Process()
outside the struct and pass a ton of arguments to it (instead it access to the members of the struct).
Upvotes: 8
Views: 17757
Reputation: 17614
Another option would be to define the func
as a type:
type Process func(a *A)
Then use it as a parameter when calling your other func:
func Test(p Process)
One thing to remember is that these definitions are exactly the same thing:
func (a *A) Process() { a.MyVar }
func Process(a *A) { a.MyVar }
A pointer receiver is just a func with a local variable of a pointer to the struct. And conversely, a value receiver is a func with a local variable to a value copy of the struct.
Why would you not use a method on the struct, like option 1? There are many reasons. It does seem intuitive to group related methods onto the struct itself like option 1 (especially if coming from other OOP languages like Java or .NET where you normally stick upteen-thousand methods on a single struct). But, since you stated that the struct
itself is quite large, this smells of SoC
(that it is too large) and may need to be broken up.
Personally, the rule I follow when using option 2 above is:
func
is not using the entire struct's properties (e.g. it is only operating on a sub-set of data, or even none at all), I instead use option 2 with a pointer. (or, use an interface itself with a zero-byte struct)This allows for much easier unit testing by breaking up my struct that is "quite large" as you say, allowing me to mock up only the interface of functionality I need to support that method.
Now, func definitions are, by definitions, types themselves. So far we have this type:
func(a *A)
This can be used as an input into another method, as you asked for, like so:
func AnotherFunc(fn func(a *A)) {
a := &A{}
fn(a)
}
But to me, this makes things a bit hard to read not to mention brittle - someone could change the func definition there and break other things elsewhere.
This is where I prefer to define a type:
type Process func(a *A)
That way, I can consume it like:
func AnotherFunc(p Process) {
a := &A{}
p(a)
}
This allows you to access p
as your pointer to the func, to pass around as you like. (Note though, you don't have to access the actual pointer of p
. IOW, don't do this &p
because func
types are passed by reference in Golang anyways, just like slices
and maps
.)
Overall, you typically follow this kind of pattern when you want to break up your logic into smaller manageable (and more testable) pieces - by using smaller, more manageable AnotherFunc()
methods to export and unit test an API contract for, while hiding the internals.
http://play.golang.org/p/RAJ2t0nWEc
package main
import "fmt"
type Process func(a *A)
type A struct {
MyVar string
}
func processA(a *A) {
fmt.Println(a.MyVar)
}
func AnotherFunc(a *A, p Process) {
p(a)
}
func main() {
a := &A{
MyVar: "I'm here!",
}
AnotherFunc(a, processA)
}
Taking the concept of func types to another level, would be to ease unit testing.
You can define global variables for your Process()
function:
var Process = func(a *A)
It would continue to be used the exact same way:
func Test(p Process)
The difference now is during unit testing, you can override the function:
package mypackage_test
import "testing"
func TestProcessHasError(t *testing.T) {
// keep a backup copy of original definition
originalFunctionality := Process
// now, override it
Process = func(a *A) error {
// do something different here, like return error
return errors.New("force it to error")
}
// call your Test func normally, using it
err := Test(Process)
// check for error
assert.Error(err)
// be a good tester and restore Process back to normal
Process = originalFunctionality
}
When I get my hands on an existing codebase, these are some of the tricks I start implementing to help decouple the application from itself - and allow for more testing.
Upvotes: 8
Reputation: 40789
You can even do it directly, without an interface:
package main
import "fmt"
type A struct {
Name string
}
func (a *A) Go() {
fmt.Printf("GO: %v\n", a)
}
func Test(fn func()) {
fn()
}
func main() {
aa := &A{Name: "FOO"}
bb := (*A)(nil)
cc := &A{}
Test(aa.Go)
Test(bb.Go)
Test(cc.Go)
}
Output:
GO: &{FOO}
GO: <nil>
GO: &{}
On the playground: https://play.golang.org/p/V-q2_zwX8h
Upvotes: 9
Reputation: 9569
You can achieve this with an interface:
type Processor interface {
Process()
}
func anotherFunction(p Processor) {
p.Process()
}
...
var a A
anotherFunction(a)
Upvotes: 3